Notifications
Clear all

My robot drives like a drunk toddler

49 Posts
7 Users
2 Reactions
950 Views
(@davee)
Member
Joined: 5 years ago
Posts: 2043
 

Hi @robotbuilder,

  Thanks for your comment. As it happens, I had already trawled the links, looking for data on the Hall Effect sensors, etc., and noticed no resistors were mentioned ... however, I am too cynical to regard the absence of such information on marketplace adverts as being an indication that they would not be advisable for a reliable and repeatable implementation. Sources like AliExpress can provide useful goods at cheap prices, but I think it is generally understood that not all offerings will precisely match the impression the adverts portray. 

-----------------

Hence, I explained that external resistors might not make any difference, etc., and because there was virtually no real information, I could only make guesses. e.g. There might have been pull up resistors within the motor/sensor systems which were not easily visible.

However, the programme code comment said "Hall sensors NEED pull-ups", suggesting that if the programme did not select the INPUT_PULLUP, it didn't work at all, and hence the programmer had found that the only pull up function was that built into the microcontroller.

---------------

If the control board is a UNO, and it uses a 'genuine' ATmega328P, then the data sheet lists an I/O pin pull up as between 20kOhm and 50kOhm. If the user has bought a 'clone' board, possibly described a Nano, then it might be a ATmega328P, or it might be something quite different, broadly emulating an ATmega328P.

A feature of other cloned processors is that whilst the 'main' characteristics are close to those of the original chips, some of the 'less prominent' characteristics can be quite different. I would suggest the value of pullup resistors that were very wide tolerance on the original chip design, could easily have an even wider tolerance on a clone.  Remember, the internal pull ups were not intended to be a pull up for an active sensor. They were intended for a pin that was either not connected, or possibly connected to an optional jumper on the pcb, very close to the microcontroller, to provide a level of customisation. In these circumstances, noise pickup will be small, and the level will not change whist the circuit is powered, so rise/fall times are irrelevant.

----------- 

A 50kOhm pull up is quite weak, will have a relatively long rise time, due to stray capacitance, etc. and will also be susceptible to noise pick up. Your suggestion that a 100nF capacitor can be helpful, further supports my suggestion that failing to provide a well-chosen external pull up resistor is a poor design approach. 

A lesson I learnt several decades ago, is that electronic circuits, especially but not exclusively those based on digital logic, will often 'do their best' to function, in spite of component failures, poor design choices, and so on. The result can be a circuit that 'works' most of the time, but 'crashes' or otherwise fails, usually at the most inconvenient moment. Such circuits can be really difficult to diagnose on the bench, and usually require a systematic approach of eliminating all possible causes.

The corollary to this observation is that a circuit which is well-designed and constructed, with no faulty or inadequate components, etc., is likely to tolerate environmental stress, such as a very noisy electrical situation.

--------  

So please treat my suggestion with as many pinches of salt as you feel appropriate ... it was only a suggestion with the proviso that it may not make any difference to the problem, because the electrical properties are (in my opinion) marginal, and substantially determined by factors such as silicon processing variations, wire lengths, etc., which can vary widely.

----------

Sorry this is a rather long sermon, and may not be helpful for this particular problem, but it is provided with good intent and the hope that it will help someone.

Best wishes, Dave



   
ReplyQuote
(@scott)
Member
Joined: 5 years ago
Posts: 19
Topic starter  

@robotbuilder Great work, thanks for finding that.



   
ReplyQuote
robotBuilder
(@robotbuilder)
Member
Joined: 7 years ago
Posts: 2508
 

@scott 

Ultimately you cannot keep a robot on course unless it is some way locked to a rigid environment.

Precise movements can be made with a stepper motor, the reason they are used in printers, printer plotters and 3D printers but unlike using stepper motors in a free moving robot these motors are locked to a rigid framework. Any free running robot will lose its position and orientation unless it is in some way locked to the environment. With a remote control robot this is done visually by the human operator. LIDAR can also work. Or lock it to a GPS gridwork. Any movement off position and orientation (POSE) can be continually corrected. You cannot do this with motor encoders alone.

The idea that you make a motorized wheel with an encoder move about like the turtle robot on a screen is really not going to happen. Errors will accumulate. Do a 36 degrees turn often enough and eventually it will not result in say 100 turns by 36 degrees being exactly 3600 degrees. This will get worse the more times you execute the turn command.

 



   
ReplyQuote
TFMcCarthy
(@tfmccarthy)
Member
Joined: 2 years ago
Posts: 514
 

@scott,

You didn't post an "Introduce yourself" so I can't tell what your programming background is. Based on what I see I'm going to assume you're fairly experienced with C, maybe less with C++. And you seem to know how to build a project. Just a CYA to be sure I don't offend with my comments.

I'm not sure I understand the problem you're seeing. What you describe doesn't matchup-up with the Bill's video. His car doesn't meander all over the floor. I think it does drift but not that much. I've mentioned what I think is the reason for the dirft but when I looked at your posted code I see a heuristic set of "fudge factors" that are applied to both motors in every interation of the while loop. This is what I think produces the chaotic driving. The heuristic doesn't really addres what I think the cause of the problem. And there will be more issues when add turn commands. There are some other differences in the code from Bill's example.

This is the statement of the problem as I see it:

Essentially, you have two motors that should be running at the same speed but are actually running at different speeds. You have encoders that detect the speed of each motor. You can set the speed of each motor. You want the motors to run at the same speed.

When the motors run at different speeds you get a drift in direction, e.g.

differential

Before tackling this drift, ask the question, "Do I need to do this? Is this a BMW or toy car?"

I worked on a differential algorithm, but I'm not satisfied with it, so I don't want to post it. However, I do think the differential is the solution. I'm just not convinced it is really needed.

Also, if you're planning to allow remote programming, that's a much bigger issue, I think.


The one who has the most fun, wins!


   
ReplyQuote
(@scott)
Member
Joined: 5 years ago
Posts: 19
Topic starter  

@tfmccarthy Thanks, I'm only interested in whether the code would be adequate to pick up the signals from the encoders if they were working correctly. I'll look into what can be done to improve the signal quality.



   
ReplyQuote
TFMcCarthy
(@tfmccarthy)
Member
Joined: 2 years ago
Posts: 514
 

Posted by: @scott

I'm only interested in whether the code would be adequate to pick up the signals from the encoders if they were working correctly.

I don't understand.

Why do you think the encoders aren't working correctly? Even if they weren't working properly, how would that affect the motor speed?

In Bill's DualMotorSpeedDemo, both motors run at full speed and the speed is never changed. The output indicates the motors don't run at the same speed.

motor RPM

The motor speeds differ from each other (given the same speed setting) and neither has a consistent speed (each with a variance of ~3 RPM). Motor 1 runs faster (also by ~3 RPM) than motor 2.


The one who has the most fun, wins!


   
ReplyQuote
robotBuilder
(@robotbuilder)
Member
Joined: 7 years ago
Posts: 2508
 

@tfmccarthy 

Encoders must provide clean signals and consistent counts as no matter what your code is it will give errors if given bad encoder data.

At the same PWM value the speeds can be different for each motor and the speed can change according to variable forces required to move the robot. So if a robot hits carpet or a slope its actual speed will change although the PWM is constant. That is why the PWM value has to be adjusted by accurate encoder counts per unit of time to maintain any desired speed. They must give an accurate measure of distance travelled.

You test the encoders by seeing if the encoder count corresponds with how much the wheel turns nothing to do with the actual speed. The encoders measure how far the wheel has travelled. I put a white dot on the wheel and ran it at different PWM values for a fixed number of encoder counts to see if the wheel always turned the same amount with each run.

The links I provided give the kind of code required,  I wouldn't refer to Bill's example code.

 



   
ReplyQuote
TFMcCarthy
(@tfmccarthy)
Member
Joined: 2 years ago
Posts: 514
 

Before I go any further, I need to clarify something.

Posted by: @scott

Posted by: @robotbuilder

Count 1000 pulses and the wheel turns x degrees and then count another 1000 pulses and the wheel doesn't turn by the same amount?

Yes, afraid so.

I understand the idea, but the test description is a bit off. You want to verify that a given number of pulses produce the correct number of rotations. This is a non-trivial calibration test.

This test is missing from Bill's video. He wasn't aiming for super accuracy.

The test verifies the number of pulses of a single rotation. You have a marker for the start location aligned with a marker on the wheel and then, at the slowest speed, count the number of pulses for a single rotation. Verify that the markers align.

Then increase the pulses by a factor of 10 and repeat. Again, the markers should align.

Repeat for a factor of 100, 1000.

At the end of the test, you'll have an idea of how accurate the encoder is.

So, @scott, I assume you ran such a test and that's why you said the encoder doesn't return consistent results.

Is that correct?

If that's the case, then the encoder is bad. I just find it very unusual that both encoders would be faulty.

 


The one who has the most fun, wins!


   
ReplyQuote
TFMcCarthy
(@tfmccarthy)
Member
Joined: 2 years ago
Posts: 514
 

Posted by: @robotbuilder

The links I provided give the kind of code required, 

I missed something. Which link is that, again?


The one who has the most fun, wins!


   
ReplyQuote
robotBuilder
(@robotbuilder)
Member
Joined: 7 years ago
Posts: 2508
 

Posted by: @tfmccarthy

Posted by: @robotbuilder

The links I provided give the kind of code required, 

I missed something. Which link is that, again?

Back in previous posts somewhere but it doesn't matter it would just be a distraction.

When I get home I will discuss how to achieve the desired results in another thread along with code solutions tested on one of my robots.

However all encoder controlled robots will over time drift from the desired changes in position and direction and to improve that you need to fuse it with a MPU6050 (gyro).

@scot posted that his most successful code is was this if you want to look at it but it needs work and isn't the completed project he is talking about.

 

/************ MOTOR PINS ************/
// LEFT motor (L298N Motor B)
const int enL  = 11;
const int inL1 = A2;
const int inL2 = A3;

// RIGHT motor (L298N Motor A)
const int enR  = 10;
const int inR1 = A0;
const int inR2 = A1;

/************ ENCODER PINS ************/
// IMPORTANT: confirmed mapping
const byte encRight = 2; // RIGHT motor encoder
const byte encLeft  = 3; // LEFT motor encoder

/************ ENCODER COUNTS ************/
volatile long leftCount = 0;
volatile long rightCount = 0;

/************ TUNING ************/
const int BASE_SPEED = 180;
const int CORRECTION = 10; // try 8–15
const int SLOW_DIST = 300; // pulses before target to slow down

/************ ISRs ************/
void leftEncoderISR() { leftCount++; }
void rightEncoderISR() { rightCount++; }

/************ MOTOR HELPERS ************/
void leftForward(int spd) {
  digitalWrite(inL1, HIGH);
  digitalWrite(inL2, LOW);
  analogWrite(enL, spd);
}

void rightForward(int spd) {
  digitalWrite(inR1, HIGH);
  digitalWrite(inR2, LOW);
  analogWrite(enR, spd);
}

void leftReverse(int spd) {
  digitalWrite(inL1, LOW);
  digitalWrite(inL2, HIGH);
  analogWrite(enL, spd);
}

void rightReverse(int spd) {
  digitalWrite(inR1, LOW);
  digitalWrite(inR2, HIGH);
  analogWrite(enR, spd);
}

void stopAll() {
  analogWrite(enL, 0);
  analogWrite(enR, 0);
  digitalWrite(inL1, LOW);
  digitalWrite(inL2, LOW);
  digitalWrite(inR1, LOW);
  digitalWrite(inR2, LOW);
}

/************ MAIN MOVE FUNCTION ************/
void MoveBothMotors(long targetPulses, bool forward) {

  leftCount = 0;
  rightCount = 0;

  while (leftCount < targetPulses || rightCount < targetPulses) {

    long error = leftCount - rightCount;

    // Deadband to ignore tiny noise
    if (abs(error) < 3) error = 0;

    int baseSpeed = BASE_SPEED;

    // Slow down as we approach the target
    long remainingL = targetPulses - leftCount;
    long remainingR = targetPulses - rightCount;
    long remaining = min(remainingL, remainingR);

    if (remaining < SLOW_DIST) baseSpeed = 130;
    if (remaining < 100) baseSpeed = 90;

    int leftSpeed = baseSpeed;
    int rightSpeed = baseSpeed;

    // Speed correction
    if (error > 0) {
      // left ahead
      leftSpeed -= CORRECTION;
      rightSpeed += CORRECTION;
    }
    else if (error < 0) {
      // right ahead
      leftSpeed += CORRECTION;
      rightSpeed -= CORRECTION;
    }

    leftSpeed = constrain(leftSpeed, 0, 255);
    rightSpeed = constrain(rightSpeed, 0, 255);

    // LEFT motor
    if (leftCount < targetPulses) {
      if (forward) leftForward(leftSpeed);
      else leftReverse(leftSpeed);
    } else {
      analogWrite(enL, 0);
    }

    // RIGHT motor
    if (rightCount < targetPulses) {
      if (forward) rightForward(rightSpeed);
      else rightReverse(rightSpeed);
    } else {
      analogWrite(enR, 0);
    }

    // SERIAL DEBUG
    Serial.print("L: ");
    Serial.print(leftCount);
    Serial.print(" R: ");
    Serial.println(rightCount);

    delay(10);
  }

  stopAll();
}

/************ SETUP ************/
void setup() {
  Serial.begin(9600);

  pinMode(enL, OUTPUT);
  pinMode(inL1, OUTPUT);
  pinMode(inL2, OUTPUT);

  pinMode(enR, OUTPUT);
  pinMode(inR1, OUTPUT);
  pinMode(inR2, OUTPUT);

  // Hall sensors NEED pull-ups
  pinMode(encLeft, INPUT_PULLUP);
  pinMode(encRight, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(encLeft), leftEncoderISR, FALLING);
  attachInterrupt(digitalPinToInterrupt(encRight), rightEncoderISR, FALLING);

  delay(1500);

  // TEST RUNS
  Serial.println("FORWARD 3000");
  MoveBothMotors(3000, true);

  delay(3000);

  Serial.println("REVERSE 3000");
  MoveBothMotors(3000, false);
}

void loop() {
}

 

 

 

 



   
ReplyQuote
TFMcCarthy
(@tfmccarthy)
Member
Joined: 2 years ago
Posts: 514
 

Posted by: @robotbuilder

@scot posted that his most successful code is was this if you want to look at it but it needs work and isn't the completed project he is talking about.

I did look at the code.

::aside::

@scot, please take this as constructive critique. This is my perspective.

::aside end::

In the sketch, in each iteration of the while loop, an error calculation is made

while (leftCount < targetPulses || rightCount < targetPulses) {
    long error = leftCount - rightCount;
    if (abs(error) < 3)     // Deadband to ignore tiny noise
        error = 0;

    //...
}

Both motors are then set to a calculated basespeed,

//...

int leftSpeed = baseSpeed;
int rightSpeed = baseSpeed;

//...

At this point speed a correction is made according to the error value.

const int CORRECTION = 10; // try 8–15

//...

// Speed correction
if (error > 0) {
    // left ahead
    leftSpeed -= CORRECTION;
    rightSpeed += CORRECTION;
}
else if (error < 0) {
    // right ahead
    leftSpeed += CORRECTION;
    rightSpeed -= CORRECTION;
}

This will produce an oscillation in the motor speeds. First, one motor goes above, the other goes below the basespeed by a fixed amount. Then they reverse. If they pass through the deadband then the speed resets to the basespeed and the whole thing starts over. It never converges.


The one who has the most fun, wins!


   
ReplyQuote
robotBuilder
(@robotbuilder)
Member
Joined: 7 years ago
Posts: 2508
 

@tfmccarthy 

It never converges.

Spot on you are good at this I tend to be lazy with figuring out other people's code.

 



   
ReplyQuote
TFMcCarthy
(@tfmccarthy)
Member
Joined: 2 years ago
Posts: 514
 

Posted by: @robotbuilder

Spot on you are good at this I

Thank you. But even I get flummoxed with things both small and large and have to call in reinforcements. In this case, I need a Mechanical.

Hey @lee-g! are you reading this?

I'm having a problem with the gear ratio equation that ChatGPT produced.

I was bothered because I couldn't get the units to match up.

The statement was:

"So at the wheel output, you get roughly:

  • 8 x 120 = 960 pulses per full wheel revolution (approx)"

That doesn't add up.

A "wheel rotation" is a shaft rotation. It's a geared motor so there's an input and output shaft. The encoder is on the input shaft (I missed this) and is a hall effect sensor with 8 magnets. The gear ratio is 120 : 1.

Now, no matter which shaft you use, a single rotation won't produce more than 8 pulses. The above equation uses 8 input rotations to produce 960 output rotations. AFAICT, the number of pulses is 64.

My problem is I couldn't put units on this. There's a missing time component that would make it more tangible., like RPM. In MKS it would be RPS.

Can you help me get the gear ration equation right and put units on it?

(At the end of the day, I think it's 2 rotations per second.)

If not, then thanks for your time.

Any other Mechanical out there that can shed some light?


The one who has the most fun, wins!


   
ReplyQuote
noweare
(@noweare)
Member
Joined: 6 years ago
Posts: 219
 

@tfmccarthy

I read it as the motor shaft has 8 counts per rotation. The gear ration is 120:1 (input shaft rotation: output shaft rotation).
The encoder is on the motor shaft so for one rotation of a wheel (output shaft) you get 120 * 8 counts on the input shaft (960)

Just to muddy the waters I looked at a digikey spec sheet on this motor and it says the input has 16 pulses per rotation not 8.



   
ReplyQuote
TFMcCarthy
(@tfmccarthy)
Member
Joined: 2 years ago
Posts: 514
 

@noweare,

Thank you. That clears that up for me. I had them reversed.

Posted by: @noweare

I looked at a digikey spec sheet on this motor and it says the input has 16 pulses per rotation not 8.

I saw something like this as well.

Thanks again.


The one who has the most fun, wins!


   
ReplyQuote
Page 2 / 4