Notifications
Clear all

My robot drives like a drunk toddler

49 Posts
7 Users
2 Reactions
4,277 Views
(@scott)
Member
Joined: 6 years ago
Posts: 19
Topic starter  

Posted by: @robotbuilder
↑

In my experience, the most common issue was the encoder signal being too noisy, causing the robot to jitter. I solved this by adding a small 100nF capacitor across the encoder signal lines and ensuring the motor driver had a clean ground path. Once these issues were resolved, the robot moved with remarkable smoothness, demonstrating the effectiveness of the arduino dc motor with encoder setup.

This sounds promising. Excuse my ignorance, but "across the encoder signal lines" means a capacitor between "A" and "B" in these sensor signal lines? And it should probably be soldered on at the motor end of the lines? 

 

IMG 20260430 144951

(I've not found any helpful information on the "DFRobot" forum yet, a lot of the useful looking forum links go to .404 error pages).

 

 



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

@scott 

That isn't something I have done it comes from,

https://www.aliexpress.com/i/32665572840.html

Scroll down and click the third [ View more ] button.

You connect a capacitor 10 to 100nF  from A to GND and another capacitor from B to GND close to the pins on the microcontroller input not the motor.

Ideally you need someone with an oscilloscope and this motor+encoder to view the actual wave shape.

 



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

After pondering the encoder motor steering problem and getting unsatisfactory results I decided to use GitHub CoPilot on the problem. I had two queries in mind:

  1. How to test the motor speed
  2. How to control the motors using a direction angle.

The results were surprising and I found them very helpful. Here's the chat:


Using the motor encoder, how can I test if a motor speed matches, is faster, or slower for a given PWM speed setting?


GitHub Copilot

Use the encoder to measure actual shaft speed over a fixed time window, convert encoder ticks => RPM (or ticks/sec), compare that measured speed to the expected speed for the applied PWM (using a calibrated mapping or slope), and classify the result with a tolerance band as “match”, “faster”, or “slower”.

Example Arduino-style implementation (interrupt-driven encoder counter, fixed measurement window, simple comparator):

// Minimal encoder speed test: counts encoder pulses with ISR,
// measures ticks over a window, computes RPM and compares to expected.

volatile long encoderTicks = 0;      // incremented in ISR
const int encoderPinA = 2;           // must be interrupt-capable
const int motorPwmPin = 5;
const int ticksPerRevolution = 20;   // encoder pulses per wheel revolution
const unsigned long measureMs = 200; // measurement window

void IRAM_ATTR onEncoder() { encoderTicks++; }

void setup() {
    pinMode(encoderPinA, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(encoderPinA), onEncoder, RISING);
    pinMode(motorPwmPin, OUTPUT);
    Serial.begin(115200);
}

// measure ticks during a measurement window (ms) and return rpm
float measureRpm(unsigned long windowMs) {
    noInterrupts();
    long startTicks = encoderTicks;
    interrupts();

    delay(windowMs); // let the motor run during measurement window

    noInterrupts();
    long endTicks = encoderTicks;
    interrupts();

    long ticks = endTicks - startTicks;
    float revolutions = (float)ticks / ticksPerRevolution;
    float minutes = (float)windowMs / 60000.0f;
    float rpm = revolutions / minutes;
    return rpm;
}

// classify measurement against expected with tolerance percent
// returns -1 if slower, 0 if match (within tol%), 1 if faster
int classifySpeed(float measuredRpm, float expectedRpm, float tolPercent) {
    float low = expectedRpm * (1.0f - tolPercent / 100.0f);
    float high = expectedRpm * (1.0f + tolPercent / 100.0f);

    if (measuredRpm < low)
        return -1;
    if (measuredRpm > high)
        return 1;
    return 0;
}

// Example usage: apply PWM, wait for settle, measure and classify
void testMotorForPwm(uint8_t pwmValue, float expectedRpm, float tolPercent = 10.0f) {
    analogWrite(motorPwmPin, pwmValue);      // set motor PWM

    delay(250);                              // let motor settle

    float rpm = measureRpm(measureMs);
    int result = classifySpeed(rpm, expectedRpm, tolPercent);

    Serial.print("PWM="); Serial.print(pwmValue);
    Serial.print(" measuredRPM="); Serial.print(rpm);
    Serial.print(" expectedRPM="); Serial.print(expectedRpm);

    Serial.print(" => ");
    if (result < 0)
        Serial.println("SLOWER");
    else if (result > 0)
        Serial.println("FASTER");
    else
        Serial.println("MATCH");
}

Practical notes and gotchas

  • You need a calibration function (sweep PWM => measure RPM) to produce expectedRpm for each PWM; motor/load/brake produce non-linear mapping. Store results in a lookup table or fit a linear model.
  • Use a measurement window long enough to reduce quantization noise (100–500 ms) but short enough for responsiveness; increase ticksPerRevolution or gear down to improve resolution.
  • Disable/avoid other code that toggles interrupts while reading encoderTicks; read volatile counters inside noInterrupts()/interrupts() pair or use atomic access.
  • Filter jitter (moving average or median) and account for acceleration: measure after a settle delay, or measure repeatedly and require consecutive classifications to avoid flapping.
  • If motor can go reverse, preserve sign of ticks or detect direction with a second encoder channel (quadrature).
  • Use RPM vs. PWM tolerance (e.g., 5–15%) appropriate to your hardware and application.

AI Caveat emptor

...Arduino-style...

doesn't match

IRAM_ATTR

which is ESP32 syntax

Other than that and a few minor style issues, I was able to easily adapt the code into a Google gtest

TEST(utMotorControl, classifySpeed_slower)
{
	int targetPMW{ 0 };
	for (int targetPWM = 0; targetPWM <= 255; targetPWM += 50) {
		float expectedRpm{ float(map_range(targetPMW, MIN_PMW, MAX_PMW, MIN_RPM, MAX_RPM)) };
		float measuredRpm{ expectedRpm - deltaRpm };
		EXPECT_EQ(SLOWER, classifySpeed(measuredRpm, expectedRpm, tolPercent))
			<< "targetPMW: " << targetPMW
			<< ", expectedRpm: " << expectedRpm
			<< ", measuredRpm: " << measuredRpm
			;
	}
}

Repeat for faster and match


In the Arduino UNO sketch RobotCar how can I calculate the motor speeds for a given angle between 0 and 180 degrees?


GitHub Copilot

Use the angle to derive a steering factor in [-1, +1] where 90° is straight; keep the outer wheel at full baseSpeed and scale the inner wheel down so an extreme angle yields one wheel at baseSpeed and the other at 0. Example implementation:

// Calculate left/right motor PWM (0..255) from
// steering angle (0..180)
// angle == 90 => straight;
// angle == 0 => full left;
// angle == 180 => full right
void calculateMotorSpeeds(int angle, int baseSpeed, int& leftOut, int& rightOut) {
    // clamp inputs
    if (angle < 0)
        angle = 0;
    else if (angle > 180)
        angle = 180;

    if (baseSpeed < 0)
        baseSpeed = 0;
    else if (baseSpeed > 255)
        baseSpeed = 255;

    // steering factor in [-1,1]
    float steer = (angle - 90) / 90.0f;

    if (steer >= 0.0f) {
        // turning right:
        // left (outer) stays at base,
        // right (inner) reduced
        leftOut = baseSpeed;
        rightOut = (int)round(baseSpeed * (1.0f - steer));
    }
    else {
        // turning left:
        // right (outer) stays at base,
        // left (inner) reduced
        rightOut = baseSpeed;
        leftOut = (int)round(baseSpeed * (1.0f + steer)); // steer is negative
    }

    // clamp outputs
    if (leftOut < 0)
        leftOut = 0;
    else if (leftOut > 255)
        leftOut = 255;

    if (rightOut < 0)
        rightOut = 0;
    else if (rightOut > 255)
        rightOut = 255;
}

Usage example:

  • calculateMotorSpeeds(90, 200, leftPWM, rightPWM);

    => leftPWM==rightPWM==200

  • calculateMotorSpeeds(0, 200, leftPWM, rightPWM);

    => leftPWM==0, rightPWM==200 (full left)

  • calculateMotorSpeeds(180, 200, leftPWM, rightPWM);

    => leftPWM==200, rightPWM==0 (full right)

Notes/gotchas:

  • This keeps the outer wheel at baseSpeed; if you want constant overall speed (both wheels scaled), use a symmetric blending formula instead.
  • Account for motor direction pins separately if you need reverse motion; this function only computes magnitudes (PWM).
  • Tune baseSpeed < 255 to leave headroom for acceleration and to avoid saturation.

This was an approach I didn't think of and a more direct solution. Also, easily adapted to gtest

TEST(utMotorControl, calculateMotorSpeeds_90)
{
	int angle{90};
	int baseSpeed{200};
	int leftPWM{};
	int rightPWM{};

	calculateMotorSpeeds(angle, baseSpeed, leftPWM, rightPWM);

	EXPECT_EQ(baseSpeed, leftPWM);
	EXPECT_EQ(baseSpeed, rightPWM);
}

Repeat for full right and left, et al.

In the end I think this clears the path towards handling input commands to control the car.


The one who has the most fun, wins!


   
Lee G reacted
ReplyQuote
robotBuilder
(@robotbuilder)
Member
Joined: 7 years ago
Posts: 2581
 

@tfmccarthy 

Probably need to start a new thread?

Do you have an actual robot base to test your code?

As I mentioned earlier the last time I was experimenting I was using the PC + webcam robot base and it used visual feedback to lock onto the environment and it controlled the motors in a simpler way much as we would controlling a radio controlled car using our eyes.

With regards to using AI generated code it is important I think that you actually fully understand how the code works. As far as I know AI does not invent new ideas. I like to think them up myself or at least understand any solution to the extent I could sit down and code it myself without copying anything directly.

Inspired by this thread I decided last night to revisit the reading of encoders and use of PID and odometry code. Maybe get a gyro sensor working as well. The motor encoders do have the A and B encoder outputs which I played with using together but in the code below I have just used the A output from each motor encoder. I have four buttons that can be used in any code example.

This is the Arduino robot base I am using.

top
bottom

 

This piece of code shows the pin connections used along with a simple loop to test if the motors turn exactly one rotation for a given number of encoder counts.

 

// Motor A
int enA = 11;
int in1 = 10;
int in2 = 9;
// Motor B
int in3 = 6;
int in4 = 7;
int enB = 5;

int counter1 = 0;
int counter2 = 0;

// button pins assignments
const int btn0 = 24;
const int btn1 = 26;
const int btn2 = 28;
const int btn3 = 30;

void forwardA(int rate){
  digitalWrite(in1, HIGH);
  digitalWrite(in2, LOW);
  analogWrite(enA, rate);  
}

void forwardB(int rate){
  digitalWrite(in3, HIGH);
  digitalWrite(in4, LOW);
  analogWrite(enB, rate);  
}

void reverseA(int rate){
  digitalWrite(in1, LOW);
  digitalWrite(in2, HIGH);
  analogWrite(enA, rate);  
}

void reverseB(int rate){
  digitalWrite(in3, LOW);
  digitalWrite(in4, HIGH);
  analogWrite(enB, rate);  
}

void turnOffMotorA(){
  digitalWrite(in1, LOW);
  digitalWrite(in2, LOW);
}

void turnOffMotorB(){
  digitalWrite(in3, LOW);
  digitalWrite(in4, LOW);
}


void setup()
{

  // Set all the motor control pins to outputs
 
  pinMode(enA, OUTPUT);
  pinMode(enB, OUTPUT);
  pinMode(in1, OUTPUT);
  pinMode(in2, OUTPUT);
  pinMode(in3, OUTPUT);
  pinMode(in4, OUTPUT);

   pinMode(btn0,INPUT_PULLUP);
   pinMode(btn1,INPUT_PULLUP);
   pinMode(btn2,INPUT_PULLUP);
   pinMode(btn3,INPUT_PULLUP);

  pinMode(3,INPUT); // set as input
  digitalWrite(3,HIGH); // enable internal pullup resister
  attachInterrupt(digitalPinToInterrupt(3), encoder1, RISING); // interrupt initialization
  
  pinMode(2,INPUT); // set as input
  digitalWrite(2,HIGH); // enable internal pullup resister
  attachInterrupt(digitalPinToInterrupt(2), encoder2, RISING); // interrupt initialization
 
  
  counter1 = 0;
  counter2 = 0;
  
  Serial.begin( 9600 );
  Serial.println("Starting up");
  
}

// interrrupt server routines for reading encoder

void encoder1()
{
  counter1 = counter1 + 1;
}

void encoder2()
{
  counter2 = counter2 + 1;
}


void loop()
{

  if (!digitalRead(btn0)){   // if button is pressed start motors
    forwardA(180);
    forwardB(180);

    counter1 = 0;
    counter2 = 0;

    while (counter1 < 520) {    // this will result in one wheel rotation
      Serial.print(counter1);
      Serial.print("  ");
      Serial.println(counter2);
    }

    turnOffMotorA();
    turnOffMotorB();

  }
}

 

 

 

 



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

Posted by: @robotbuilder
↑

Probably need to start a new thread?

From my POV, no.

This is Scott's project, his thread. I don't want to divert from that. He still has a ways to go to get to the finish line. He has a command control interface to design.

Your "Robot Base - new build" thread is still available to post activity in.

Posted by: @robotbuilder
↑

Do you have an actual robot base to test your code?

Not at the moment. I do unit testing with Google Test. If I really need the hardware, I tend to get minimum I need for testing, so I have more parts than kits. (Too many parts, if I'm honest about it.) Because of all the posts on cars lately, I've been thinking about getting a kit, but I don't really want a RC car. I wouldn't use it much. I'd end up giving it away to some kid. Because I'm so cheap I'd feel both generous and foolish. Still, I might buy a few motors and some wheels. Sadly, the house maintenance budget has exploded recently so my purchasing power has evaporated. Woe is me.


The one who has the most fun, wins!


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

@tfmccarthy 

@scott was apparently not getting reliable counts off the encoders. He hasn't indicated if that is still a problem.

Was it a hardware issue or a software issue or both?

Ideally code that works with my motorized wheels with encoders should also work with his but we have to trial it on both robots but he never got back.

Looking again at the code he said gave the most successful is from some unknown source, it isn't from Bill's example. I don't know from it exactly how he has wired it up.

 

/************ 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() {
}

I just asked ChatGPT how do I get reliable counts given the above code is the best code so far.

You’re actually very close already—your structure is solid. The inconsistency you’re seeing isn’t because your logic is “wrong,” it’s because you’re right on the edge of what simple pulse counting can handle with noisy TT motor encoders and an L298N Motor Driver Module.

Here’s the blunt truth:
👉 leftCount++ on a single edge will never be perfectly reliable unless you harden it a bit.

Let’s fix that properly.


🔧 What’s causing your inconsistency

1. Electrical noise (biggest problem)

  • Brushed motors + L298N = a lot of noise
  • That noise shows up as fake pulses

2. You’re counting only FALLING

  • If the signal bounces → you get double counts

3. No debounce / filtering

  • Even Hall encoders can jitter at transitions

✅ Fix #1: Add software filtering (huge improvement)

Add a minimum time between pulses inside your ISR.

🔥 Replace your ISRs with this:

 
volatile long leftCount = 0;
volatile long rightCount = 0;

volatile unsigned long lastLeftTime = 0;
volatile unsigned long lastRightTime = 0;

const unsigned long MIN_PULSE_GAP = 200; // microseconds (tune this)

void leftEncoderISR() {
unsigned long now = micros();
if (now - lastLeftTime > MIN_PULSE_GAP) {
leftCount++;
lastLeftTime = now;
}
}

void rightEncoderISR() {
unsigned long now = micros();
if (now - lastRightTime > MIN_PULSE_GAP) {
rightCount++;
lastRightTime = now;
}
}
 

🧠 Why this works

  • Ignores pulses that arrive “too fast” to be real
  • Kills noise without hurting real counts

👉 Typical values:

  • Start with 200 µs
  • Try 150–400 µs depending on speed

✅ Fix #2: Switch to RISING instead of FALLING

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

👉 Often more stable depending on encoder hardware


✅ Fix #3: Remove delay(10) (this matters more than it looks)

That delay is:

  • Slowing your correction loop
  • Letting motors drift before correction kicks in

Replace with:

 
delay(2);
 

or even:

 
// no delay at all
 

✅ Fix #4: Protect shared variables properly

You reset counts like this:

 
leftCount = 0;
rightCount = 0;
 

⚠️ That can glitch if an interrupt fires mid-write.

Fix:

 
noInterrupts();
leftCount = 0;
rightCount = 0;
interrupts();
 

✅ Fix #5: Clamp your correction harder

Right now:

 
const int CORRECTION = 10;
 

That’s often too weak for TT motors.

👉 Try:

 
const int CORRECTION = 20;
 

⚡ Fix #6 (hardware – VERY effective)

If you do only one hardware fix, do this:

Add capacitors:

  • 0.1 µF between:
    • encoder signal → GND
  • AND
    • motor terminals (reduces EMI)

This dramatically stabilizes counts.


🚀 Optional upgrade (big jump in accuracy)

Right now you’re using single-channel counting.

If you want really consistent motion:

👉 Add direction-aware counting (use both encoder pins per motor)

But since you're on an Arduino Uno R3:

  • Keep your current interrupt pins
  • Read the second channel inside the ISR

(only if your encoder exposes both A/B pins)


🧪 Quick sanity test (do this!)

Run motors at constant speed and print:

 
L: xxxx R: xxxx
 

👉 What you want:

  • Counts increase smoothly
  • Left/right difference stays small (< ±20 over long runs)

🎯 What “good” looks like

After fixes:

  • No random jumps
  • Repeatable distances (within ~2–5%)
  • Straight driving without oscillation

🧠 Bottom line

Your code logic is already better than most people write.

The real issue is:

signal quality, not algorithm quality

Fix that with:

  • pulse filtering ✅
  • less delay ✅
  • proper interrupt handling ✅
  • noise suppression ✅

 

 



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

@tfmccarthy 

Still, I might buy a few motors and some wheels. Sadly, the house maintenance budget has exploded recently so my purchasing power has evaporated. Woe is me

Real hardware doesn't always (maybe never) work as a theoretical hardware model so it is required to check if the software works with it. Cost is an issue for me as well the reason I use old vacuum robot parts to get the parts cheap.

but I don't really want a RC car.

Nor do I.

It is a sort of "hello world" for hardware. Beyond that robot projects teach electronics and programming hardware useful any other practical projects. It involves software, hardware and mechanics.

 



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

@scott,

Was this helpful or not helpful?

Were you able to run the code and test the motors?


The one who has the most fun, wins!


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

Thanks all,

I still believe that the encoder signals are at fault from my experiments driving the robot and from counting the wheel revolutions when set to a particular count. I'm going to do what I can to protect the signal with a couple of capacitors and some attention given to the ground connections and wire routes. 

It's going to be a little while for me to get around to this so you're welcome to take the conversation where you like. I'm going to knock this activity back to Christmas giving me more time to get this ironed out. Alternatively I might just buy an arduino Alvik: 

image

Many thanks for all the help.

Scott.

 



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

https://dronebotworkshop.com/alvik/

 



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

Just a thought, but does anybody here live in the UK and fancy taking a look at this if I were to post it to them? You're welcome to make whatever alterations you think are necessary. 

Also, might it be possible for a moderator to change the title of this thread to something like "6v TT motors with encoders and gearboxes" to make it more useful for other people searching this problem in the future?

Thanks again,



   
ReplyQuote
Yassin
(@yassin)
Member
Joined: 9 months ago
Posts: 47
 

@scott

I’ve been following your thread about your robot’s jittery movement and encoder noise issues, and I totally get how frustrating unstable motor and power connections can be.
 
If you need lithium‑ion power connectors rated for 10–120A for your robot’s battery, motor drivers, or power wiring, I can send you some samples completely free for you to test and use. Just let me know if you’d find them helpful!

Yassin | Building Compact, High-Current Connections for Drones & Robots


   
ReplyQuote
byron
(@byron)
No Title
Joined: 7 years ago
Posts: 1283
 

@scott

I'm in the UK and I'm supposing what you had in mind was to ship out your bot in the hope it's returned loaded with code that enables the bot to drive straight as an arrow.  😀  

But even if the voltage sent to the motors (PWM) is constantly adjusted to enable both the motors to achieve the same encoder count it does not necessarily equate to the same distance each of the wheels may travel due to small wheels being bumped about going over ruggs, or door way thresholds, or may be the odd crumb or two perhaps lifting a wheel momentary meaning spins in the air with no travel.   These things add up, though for driving within a room with a smooth floor for a short distance, a straight'ish  path is normally achievable.

A year or so back I was getting an outdoor bot to travel without too much meandering but that was by using GPS positioning systems.   I had previously had a play at getting as small indoor bot with some cheapo motors and optical encoders. That was using an arduino uno and a pololu motor driver of some sort.   Remembering I based some of my code from a SparkFun video I had a quick search and found it readily enough, link below: 

But I haven't re-watched it to see if it's of any relevance to your setup.

When the Raspberry pi pico came out with its PIO facility that enables little state machines to offload pulse counting from the CPU I had in mind to have a go at using a pico in a small bot just to have a play to see how the PIO worked, but I never got round to it.  If indeed you did have it in mind to ship out your bot,  I would have a go with programming it with a rpi pico, but I would do it in micropython, probably not what you had in mind at all. 😎  

Also, if your encoder counts for an individual motor are proving inconsistent then it may not end well.   I would start a test be simply running the motors with the wheels off the ground and test each motor individually at various PWM levels (e.g. repeat encoder pulse counters for the same millis timebase) to get a good feel of the consistency of each motor individually.  (Of course its not expected that the motors would be consistent with each other)

 



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

@byron 

Also, if your encoder counts for an individual motor are proving inconsistent then it may not end well.

I assumed this was the problem?

@scott
Months later I'm baffled and wondering if it's possible to get a reliable count off these encoders at all. It's not that they're hopeless, but they're inconsistent enough to have the robot consistently veer off to one side or turn by much too much. The unreliability isn't quite reliable enough to just compensate for either.

Without consistent accurate encoder counts the whole exercise is pointless.

With consistent accurate encoder counts and odometry code the robot can compute where it is and auto  correct the PWM values to follow some given path although ultimately errors will accumulate and its actual position will be lost without other sensors being used. 

Also @scott indicated the need to program the robot not simply have a remote control toy.

I wanted to create a simple differential drive robot for my niece to program to drive forward, turn right 90, etc.

Simple coding fun for her I thought...

This implies some set of functions which at the highest level would be give a desired goal position (navigation between two positions).

But it could start with a set of simple functions.

It is essentially turtle graphics

import turtle

t = turtle.Turtle()

for _ in range(4):
t.forward(100)
t.left(90)

turtle.done()

 

Unfortunately I don't have the particular set of motors used by @scott so I can't really experiment with getting consistent accurate encoder counts.

 



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

Posted by: @byron
↑

But even if the voltage sent to the motors (PWM) is constantly adjusted to enable both the motors to achieve the same encoder count it does not necessarily equate to the same distance each of the wheels may travel due to...

From my build the Elegoo SmartCar v4.0 I noticed something that is a common problem with real cars: wheel alignment. The wheels on the toy are press fit and then screwed down. The wheel alignment is often misaligned not to mention the silicon tires are not seated correctly. The wheel mounts are also misaligned. You can see it clearly when testing: the wheel wobbles. It's no surprise the car drifts, with or without encoders.

An easy and interesting test is to run the car forwards and backwards a short distance at slow speed. (All the Elegoo demo code wants to run at top speed, which is foolish.) The test will show the drift very quickly.

Now the Elegoo motors don't have encoders but the car does have an MCU, and they use it to adjust the motor speeds for course correction. it works...to a degree (Elegoo says ("you will find that the car will repeatedly move forward for 3 seconds and then move backward for 3 seconds in a relatively straight line.")

I thought the problem was the driving algorithm, not the encoders.


The one who has the most fun, wins!


   
ReplyQuote
Page 3 / 4