In Case Anyone Wonders Why EasyGoPiGo3.steer() Does Not Work For Them

At this time I am only aware of a single GoPiGo3 API that does not work as designed: EasyGoPiGo3().steer()

The method claims:

   Each motor is assigned a percentage of the current speed value. 

Instead it is coded to assign each motor the requested percentage of the NO_LIMIT_SPEED which is 1000 DPS.

        self.set_motor_dps(self.MOTOR_LEFT, self.NO_LIMIT_SPEED * left_percent / 100)
        self.set_motor_dps(self.MOTOR_RIGHT, self.NO_LIMIT_SPEED * right_percent / 100)

On my robots, any percentages above 45% will cause both motors to go their “full speed” which is between 425 and 450 DPS, and different for each motor. The red board will not be able to PID the requested speeds at all.

I submitted a pull request to fix it and a test program to confirm the fix. The request was rejected due to time needed to fully test the change.

This is the code that works:

        # self.set_motor_dps(self.MOTOR_LEFT, self.NO_LIMIT_SPEED * left_percent / 100)
        # self.set_motor_dps(self.MOTOR_RIGHT, self.NO_LIMIT_SPEED * right_percent / 100)

        # Modified to WORK!! was percent of NO_LIMIT_SPEED
        self.set_motor_dps(self.MOTOR_LEFT, self.speed * left_percent / 100)
        self.set_motor_dps(self.MOTOR_RIGHT, self.speed * right_percent / 100)

Tagging @loringw in case a student tries using the steer() method.

This is the test program I used

2 Likes

What!??

That’s crazy!

You did all the work, created a test suite, verified it four ways from Sunday, and they rejected it?
picnic

This is insane.

Seriously, how much testing do they need?
:man_facepalming:

2 Likes

From you a former QA guy? I test one program, they would have to retest everything that includes EasyGoPiGo3.py - the entire API and every example program. Surely you don’t trust “I only changed two lines.”

2 Likes

I dug into the history and the very first version of steer() had these lines:

    self.set_motor_dps(self.MOTOR_LEFT, self.get_speed() * left_percent / 100)
    self.set_motor_dps(self.MOTOR_RIGHT, self.get_speed() * right_percent / 100)

Now, for some reason, it no longer does that.
See this PR: https://github.com/DexterInd/GoPiGo3/pull/209

I’ve been bad. I didn’t explicitely write the reason why but it did have a valid reason at the time.

3 Likes

Also your PR hasn’t been rejected, it just hasn’t been merged yet.

2 Likes

It’s a matter of scope.

It’s not possible to test every possible case, every single time.

What you want is the best coverage possible in a reasonable amount of time.

Thinking about this, (IMHO), it’s a matter of what you expect a “set_speed(percent)” to do.

In my case, I would expect it to set a percentage of the maximum speed, not the current speed.  (i.e.  set_speed(50%) should set the robot’s speed to whatever half speed would be, based on the maximum possible speed.)

Which is more intuitive:

  • set_speed(50%) = half of the current velocity.
    — or —
  • set_speed(50%) = half of the maximum velocity.

My vote is for the second, and the fix is to correct the documentation.

=======

Next issue:
The motor’s velocity is not linear over the entire range of possible PWM values.

This is a hardware issue caused by the fact that a motor’s response is not directly proportional to the duty-cycle of the power applied.

This is a result of friction, weight of the robot, and the combination of the armature’s inductive reactance and counter-EMF.

This would have to be researched, but what I suspect is happening is something like this:

  • For small duty-cycles, (say < 25%), the motor does not get enough power to move.
  • For larger duty-cycles, (say from 25% to 75%), the motion is somewhat linear.
  • For very large duty-cycle values, (75% or larger), the motor armature is near saturation so there is no perceptible change.

Note that these values are for example only and real values would have to be determined experimentally.

I also suspect that some kind of correction curve, (similar to a gamma correction curve for a LED’s brightness), would have to be used to make it truly linear.

This could be done in firmware, or as a software applied look-up table.

Another possibility is dynamically varying the duty-cycle to obtain the requested speed.  That is a more complex option and assumes tightly and dynamically regulated motor speed based on encoder feedback.

What say ye?

P.S.
This is what happens when you are eight hours ahead - the majority of the discussion happens while I’m asleep!

1 Like

It is not the percent of the current speed, it is a percent of the current speed limit.

Thus if you want the bot to go fwd at 150 DPS the steering percentages are going to be 100 percent both, and turns will be by slowing the appropriate motor a little to a lot.

The other case for use is where you want to implement your own PID controller and so allow for both plus and minus from a percentage value on either, but in no circumstance would having an unusable 50 percent of totally unusable percentages be expected, usable,or desirable so I think you would want to use the other APIs in the case you are thinking of.

2 Likes

My initial reaction was the same as @jimrh - why wouldn’t it be the percentage of maximum speed. But your use cases do make sense.

/K

1 Like

Isn’t “the current speed limit” the maximum allowable speed?  Or do you translate “current speed limit” to mean “current set speed”?

From the point of using the 'bot in the most obvious way possible, (see rule of least astonishment), set_speed(%) should be defined as percentage of the maximum allowable speed.

In other words, if the maximum speed is “x”, than set_speed(50%) should set the speed to one half of “x”.

The gas pedal works as a percentage of full speed and that is the paradigm that most people are familiar with.

IMHO, percentage of anything other than maximum allowable velocity is a more advanced topic and is rightly left as an exercise for the student. :wink:

1 Like

The maximum speed possible for any bot on a particular surface and particular incline is a changing unknown.

A desired maximum speed limit can be known by setting it with set_speed(dps) which sets self.speed and tells the red board via the set_motor_limits(L+R, dps=nnn DPS). The motors can be limited by a max speed or a max power, and in this case the maximum speed limit is being set.

Method set_speed(dps) could have been named set_dps_limit() or set_speed_limit() but …
The instance variable self.speed could have been named self.speed_limit but …

set_motor_limits(L+R, power=+/-127%) is used to set a power limit

  • the doc says +/-100 but passes values plus/minus 0 to 127 straight through to board

set_motor_limits(L+R, dps=+/-inf) is used to set a speed limit

set_motor_power(L+R,+/-100) is used to vary current motor power by percentage

  • may be limited by current set_motor_limits(power=%)

set_motor_dps(L+R,+/-1000) is used to set a desired speed

  • may not be achievable
  • may be limited by the current set_motor_limits(dps=nnn)

steer(L%,R%) is used to vary wheel speeds proportionally

2 Likes

Perhaps because you all were confusing yourselves everywhere with self.speed, self.DEFAULT_SPEED, self.NO_LIMIT_SPEED which are all “speed limits” not “speeds”.

This was about the time the API changed from a library_of_methods to two Classes_with_methods. I had implemented the gopigo library of methods on my RugWarriorPi robot and when you all went to classes, decided it was easier to buy a GoPiGo than understand how to convert the new classes for my non-GoPiGo robot.

2 Likes

I think we’re asking the wrong question.

What we should be asking is why NO_LIMIT_SPEED is set to such an extreme value?

And if it is desirable to have a “wide open throttle” value set like that, (which assumes a flashing red/blue light on the 'bot!), then there should be a “maximum_speed” value that is more reasonable based on the series of the encoders used, and set_speed(%) be a percentage of that value.

Either that, or my mnumonica is outta tune and I have no effing clue. . .

1 Like

Don’t get off track. This discussion is about the steer() method purpose, and if the implementation achieves that purpose.

2 Likes

This isn’t off track.

Question #1:
Should set_speed(%) be a percentage of NO_LIMIT speed or something else?

Question #2:
If it is desirable for set_speed(%) to be a percentage of NO_LIMIT speed, (for whatever reason), than why is NO_LIMIT speed set to such an extreme value?

Question #2a:
If it is desirable for NO_LIMIT speed to have an outrageous value, then should set_speed(%) be a percentage of some other, more sane, “maximum” speed instead?

1 Like
  1. something else and there is no set_speed(%) anyway
  2. NO_LIMIT has to be beyond the possible, and a safety factor of 2x was chosen. The MAX is variable, unknowable, above 300 and less than 500, so 1000 is a great number for a “Not a limit” limit. And there is no set_speed(%) method
  3. WHAT? there is no set_speed(%) but if you mean steer(%,%) then yes the “more sane maximum speed” is self.speed[limit] which is 300 by default.
2 Likes

Perhaps the reason was the elimination of the get_speed() method caused a compile error.

I don’t know how to check all the Dexter examples to see if any use this EasyGoPiGo3().steer() method and perhaps better understand why it exists and better understand its requirements.

I don’t use this method and am regretting having brought it up in the first place. Sometimes my good intentions get out of hand.

2 Likes

Silly me.
I was trying to abbreviate a long and complex expression:

self.set_motor_dps(self.MOTOR_LEFT, self.NO_LIMIT_SPEED * left_percent / 100)

self.set_motor_dps(self.MOTOR_RIGHT, self.NO_LIMIT_SPEED * right_percent / 100)

. . .to something easier to type on my telephone:

set_speed(%)

Which is essentially what that long expression does anyway.

Sort-of like the episode Juliana Xavier Huntington Sasperilla, (or something like that), in Schoolhouse Rock back in the 70’s.
(Spoiler:  Pronouns are easier.)

Since we’re talking about the global concept, (as far as I knew), that simplification should have been OK.

Guess I was wrong.

Sorry about that.

1 Like

Because, (in my obviously unknowing opinion), it makes more sense to a beginner to set speed as a percentage of a known value such as the maximum permissable speed instead of the current speed setting.

I think the big goof was twofold:

  1. Using NO_LIMIT speed as max speed.  It should have been self.speed[limit] instead.
  2. Failing to update the documentation to reflect the change.
1 Like

Is two goof-balls playing detective

2 Likes

No, I disagree.

Trying to understand what is going on under the hood is good.

  1. It makes everybody think.
  2. It helps Nicole understand what went on in Jurassic times when maybe she wasn’t the mother of GoPiGo.
  3. It helps me understand what everyone else is talking about.  Especially when everyone is talking about the various classes and methods.
     
    BTW,
    “self.” still drives me crazy because, (to me), it implies the existence of “something_else.” too.

But that’s me.

Differing ground planes in a circuit is easy.  Why everyone is absolutely class-crazy is not.  (Do we really need to instantiate five different classes just to say “Hello World!”?)

Guess I may never know.
:wink:

2 Likes