ev3dev / ev3dev-lang-python

Pure python bindings for ev3dev
MIT License
431 stars 145 forks source link

Motor speed_sp problem #220

Closed ndward closed 7 years ago

ndward commented 8 years ago

Almost all of the many problems I was having have evaporated over the last few days but I still have a couple of problems with motors:

1) Speed_sp has units of tacho counts per second which is equivalent to degrees per second for the EV3 motors. This seems to work well for higher values of speed_sp (900, for example) but seems to be extremely inaccurate for me for low values of speed_sp such as 100. If I run a large motor at speed_sp = 900 for 10 seconds I get 25 rotations, as I should (9000/360=25). If I run a large motor at speed_sp=100 for 36 seconds I should get 3600 degrees or 10 rotations but in fact I get 12.25 rots which is a 'huge' 22.5% error. What's going on? I've tried this with two different large motors and a medium motor and get the same result every time. Here is my script:

#!/usr/bin/env python3
from ev3dev.ev3 import *
from time import sleep
m = LargeMotor('outB')
m.run_timed(time_sp=36000, speed_sp=100)  # should turn 3600 degrees or 10 ROTS
# but turns 12.25 ROTS     12.25/10= 1.225 so 22.5% error!
sleep(40)

2) The documentation does not indicate what is the usable range of values for speed_sp, even though this information is more or less essential and cannot be obtained using max_speed because the theoretical maximum speed is in fact unobtainable. I assume the speed_sp range is not given because it varies from one type of motor to another, but you could at least give it for the EV3 motors. My trial and error experiments suggest that the maximum usable value for speed_sp for the large motor is about 950. Can you confirm this? (Max_speed returns 1050.) Values of 1000 used more than once consecutively can 'disable' the motor until a much lower value (such as 900, but not 950) is used again, according to my experiments. For example, if I run the following script with a large motor the speed_sp=900 runs fine but only the first speed_sp=1000 run works – the second attempt fails and the motor does nothing. UPDATE: As indicated in a later message, this appears to be a problem when stop_action is 'coast' or 'brake' but not when it is 'hold'.

#!/usr/bin/env python3
from ev3dev.ev3 import *
from time import sleep

m = LargeMotor('outB')

m.run_timed(time_sp=5000, speed_sp=**900**)
sleep(7)
m.run_timed(time_sp=5000, speed_sp=**900**)
sleep(7)
m.run_timed(time_sp=5000, speed_sp=**1000**)
sleep(7)
m.run_timed(time_sp=5000, speed_sp=**1000**)
sleep(7)

For the medium motor the maximum usable speed_sp is about 1450. Can you confirm this? It is NOT ok to use a value of 1500 according to my tests. Max_speed returns 1560.

3) I'm interested in helping beginners move from EV3-G to EV3 Python so I'd like to help them understand the relationship between EV3-G's power setting (-100 to +100) and EV3 Python's speed_sp and duty_cycle_sp values. When I tell EV3-G to rotate the large motor 100 rotations at 100% power it takes 38 seconds which gives 100/38= 2.6 rot/s or 158 rot/min or 950 deg/s. This matches nicely with my observation that the maximum usable value of speed_sp is about 950 and that using a value of 1000 or more is certain to cause problems.

When I tell EV3-G to rotate the medium motor 100 rotations at 100% power it takes 24 seconds which gives 100/24= 4.17 rot/s or 250 rot/min or 1500 deg/s. This matches quite well my observation that the maximum usable value of speed_sp is about 1450, but I believe that a value of speed_sp = 1500 is not reliable in ev3 Python even though the corresponding speed is attainable in EV3-G.

Perhaps I will also carry out some tests to see the correspondence between duty_cycle_sp and speed_sp.

WasabiFan commented 8 years ago

If I run a large motor at speed_sp=100 for 36 seconds I should get 3600 degrees or 10 rotations but in fact I get 12.25 rots which is a 'huge' 22.5% error. What's going on?

I'm not an expert on the details here, but in general if you want to go a specific distance you should use the distance-based run modes with the stop_command set to hold. That error percentage does seem like a lot, which is why I'd hope for comment from @dlech or @rhempel, but with the combination of your script not setting the stop mode (which it should to get accurate movement) and the potential small inaccuracies of the time-based mode, it is believable. If you want to run for a specific distance, configure it as I mentioned above; if you want to run for time, use the time mode. The stop_action should be set either way for these tests, though.

...and cannot be obtained using max_speed because the theoretical maximum speed is in fact unobtainable. I assume the speed_sp range is not given because it varies from one type of motor to another, but you could at least give it for the EV3 motors. My trial and error experiments suggest that the maximum usable value for speed_sp for the large motor is about 950. Can you confirm this?

It isn't just the type of motor that has an impact; the assembly that it is connected to (e.g. a wheel or gears) is much more significant. The empirical value also varies widely from motor to motor of the same type, depending on how much it has been used and defects in manufacturing. The LEGO motors have a fair amount of slop/error built into them.

The value that we supply for the expected maximum speed is the value from max_speed, and that's what we recommend that you use for your calculations; because it varies so much based on the factors I listed above, the difference between 950 and 1050 is negligible.

This matches nicely with my observation that the maximum usable value of speed_sp is about 950 and that using a value of 1000 or more is certain to cause problems.

I believe it will not cause problems. Anything above the speed setting that it can physically achieve will make no difference, because it will already be applying the maximum power. You can cause problems if you put too much strain on the motor, though.

Perhaps I will also carry out some tests to see the correspondence between duty_cycle_sp and speed_sp.

When you use the speed setpoint, the ev3dev drivers just do some math with that and set the duty cycle accordingly. I think you can even read the duty_cycle while the motor is running with speed_sp to see what the output duty cycle is. So there isn't really anything to test here, because you would be finding a relationship which is defined mathematically anyway.

ndward commented 8 years ago

I believe I get the large (more than 20%) error even with stop_action='hold'

I believe it will not cause problems. Anything above the speed setting that it can physically achieve will make no difference, because it will already be applying the maximum power.

My point is that using speed_sp values beyond a certain threshold (which is less than max_speed) can 'disable' the motor until a much smaller value is used. Try the script and if you get the same result that I get you will understand the problem.

WasabiFan commented 8 years ago

Try the script and if you get the same result that I get you will understand the problem.

Hmmm... that's quite odd. I was able to reproduce the behavior with a shell script as well (included below) so we know that it isn't a isolated to the Python library. @dlech can you comment? I'm pretty sure that this is a bug and not intentional, but I can't figure out why it is happening.

motor=/sys/class/tacho-motor/motor2

echo 5000 > $motor/time_sp
echo 900 > $motor/speed_sp
echo run-timed > $motor/command

sleep 7

echo 5000 > $motor/time_sp
echo 900 > $motor/speed_sp
echo run-timed > $motor/command

sleep 7

echo 5000 > $motor/time_sp
echo 1000 > $motor/speed_sp
echo run-timed > $motor/command

sleep 7

echo 5000 > $motor/time_sp
echo 1000 > $motor/speed_sp
echo run-timed > $motor/command

sleep 7
ddemidov commented 8 years ago

I can confirm as well. Further, I had this code to determine when the medium motor stops rotating:

m.run_to_rel_pos(position_sp=1080, speed_sp=1200)
while 'running' in m.state: sleep(0.1)

and some times this would just hang with the motor doing nothing. When I printed the motor state, it was 'overloaded' (the motor was not attached to anything, but the buttery was not at its maximum capacity).

dlech commented 8 years ago

These sound like motor driver issues rather than python issues. (So issues should be opened at https://github.com/ev3dev/ev3dev/issues if you think the drivers should be changed.)

Issue 1: Motor speed at low speeds does not seem to be accurate

This sounds like a bug in the motor drivers. I would have to look at the driver code, but this could be near the point where we decided the motor is moving too slow to measure and we call the speed 0. If someone wants to look into motor speed measurement deeply, here is a good place to start.

Issue 2: Motors are not able to run at speeds near max_speed.

The official EV3-G software has the same behavior, although our max speed is a bit higher. The actual maximum obtainable speed actually depends on battery voltage and the load on the motor, so if you repeat the experiments above with a different battery level or with the motor heavily loaded, you will get different results. See this graph. The current values of the max_speed attribute are discussed here.

There is an overloaded flag that should be returned by the status attribute that indicates when you are running at such a condition (e.g. low battery) that speed_sp cannot be maintained.

The reason we chose the 9V no-load speed as max speed is because there is no fixed max speed of the motor. Even if we had chosen the 5V no-load speed, if the motor is heavily loaded, we can still get into the overload condition.

Issue 3: Basically the same as Issue 2

The measured values for 100% sound right for a full-ish battery. They should be a bit lower for a nearly empty battery.

ndward commented 8 years ago

Regarding the maximum usable value for speed_sp, it's interesting to run the following script, assuming you get the same results as I do

#!/usr/bin/env python3
from ev3dev.ev3 import *
from time import sleep

m = LargeMotor('outB')
print(m.max_speed)   # should give 1050?

m.run_timed(time_sp=5000, speed_sp=900, stop_action='hold'); sleep(7)
m.run_timed(time_sp=5000, speed_sp=950); sleep(7)
m.run_timed(time_sp=5000, speed_sp=1000); sleep(7)
m.run_timed(time_sp=5000, speed_sp=1000); sleep(7)
m.run_timed(time_sp=5000, speed_sp=950); sleep(7)
m.run_timed(time_sp=5000, speed_sp=900); sleep(7)
Sound.beep().wait()

m.run_timed(time_sp=5000, speed_sp=900, stop_action='brake'); sleep(7)
m.run_timed(time_sp=5000, speed_sp=950); sleep(7)
m.run_timed(time_sp=5000, speed_sp=1000); sleep(7)
m.run_timed(time_sp=5000, speed_sp=1000); sleep(7) # FAILS!
m.run_timed(time_sp=5000, speed_sp=950); sleep(7) # FAILS!
m.run_timed(time_sp=5000, speed_sp=900); sleep(7)
Sound.beep().wait()

m.run_timed(time_sp=5000, speed_sp=900, stop_action='coast'); sleep(7)
m.run_timed(time_sp=5000, speed_sp=950); sleep(7)
m.run_timed(time_sp=5000, speed_sp=1000); sleep(7)
m.run_timed(time_sp=5000, speed_sp=1000); sleep(7) # FAILS!
m.run_timed(time_sp=5000, speed_sp=950); sleep(7) # FAILS!
m.run_timed(time_sp=5000, speed_sp=900); sleep(7)

My results suggest that higher values of speed_sp (up to 1000) can be used with stop_action='hold' than with stop_action='brake' or stop_action='coast' for which speed_sp=900 should not be exceeded. Can you confirm that my observations are reproducible?

WasabiFan commented 8 years ago

@dlech the issue that we've been posting scripts for is different (something you didn't mention): In the original scripts, the last run call does nothing. It appears to be caused by the 1000 speed.

dlech commented 8 years ago

OK, so 3rd issue, motor sometimes does not run at high speed_sp. If you find that the issue is reproducible, open an issue at https://github.com/ev3dev/ev3dev/issues with the exact steps to reproduce.

WasabiFan commented 8 years ago

Opened ev3dev/ev3dev#752.

ndward commented 8 years ago

One of my large EV3 motors has stopped working normally. I've used it quite a lot so it could be normal wear and tear. I hope it could not be caused by EV3 Python, specifically by the issues raised on this page (please reassure me!). The symptoms are that it always runs at a high speed no matter what the speed_sp setting (other than zero). It does respond to a negative sign normally, by running backwards. It does not respond to the motor_stop() command so I have to stop it by setting speed_sp to zero. It's definitely the motor that is defective because if I swap the motors on ports B and C the same physical motor (not the same port) continues to misbehave. Even if I use EV3-G instead of EV3dev Python I get the same behaviour. I did notice that in EV3-G If I set the power to an extremely small value like 1 or 2 then the motor speed ramps up over a few seconds before again reaching a high speed. Here's a script with comments about the bad behaviour:

#!/usr/bin/env python3

# Comments describe my damaged motor
from ev3dev.ev3 import *
from time import sleep
import math

mC = LargeMotor('outC')

mC.reset()

mC.run_forever(speed_sp=10) # very low speed_sp
# motor speed ramps up slowly 
sleep(10)

mC.stop(stop_action="hold")   
# motor runs backwards very slowly
# or sometimes does not move
sleep(10)

mC.run_forever(speed_sp=0)  # motor stops
sleep(4)

mC.run_forever(speed_sp=100) # low speed_sp
# motor runs very fast forwards, as if speed_sp=900 
sleep(10)

mC.stop(stop_action="hold")   
# motor runs backwards very fast
sleep(10)

mC.run_forever(speed_sp=0)  # motor stops
sleep(4)

So, I guess I have to buy a new motor?

WasabiFan commented 8 years ago

It sounds to me like the encoder which measures speed/rotation inside the motor has become detached or is broken in some other way. The fact that it starts slow and then ramps up makes it sound like it begins by applying a low power, then senses that it still isn't moving so it applies more power, then senses that it still isn't moving and the loop continues. In ev3dev (and likely in the standard OS as well), this control is implemented using a mathematical model called PID, which is very common in robotics. So yes, you likely have to buy a new one. It can be an interesting exercise to take these things apart when they break so you see how they work inside!

I am pretty sure that ev3dev did not cause this (there isn't much that it could be doing to break the motor); failures like this are reasonably common with the LEGO motors.

dwalton76 commented 7 years ago

@ndward any objections to closing this one?

ndward commented 7 years ago

I actually think this issue should remain open until the physical issue raised at the top of this page has been fixed. As far as I know that's not the case yet. It bothers me that EV3 Python can have such large errors sometimes - as far as I know competing languages such as EV3-G, EV3 Basic, RobotC etc do not have errors of more than 20% in certain situations and I'm really hoping someone will fix this soon. Unfortunately it's not something I can do myself.

WasabiFan commented 7 years ago

The observed behavior where the motor doesn't run at all isn't an issue with the Python library; it was an issue with the ev3dev OS. The issue has been fixed as of the 17-ev3dev kernel (https://github.com/ev3dev/ev3dev/issues/752). Is there something else that hasn't been resolved?

ndward commented 7 years ago

I'm delighted to hear that issue number one mentioned in my first message has been solved - congratulations to whoever fixed it. Do you think that issue number 2 in my first message has also been fixed? It was to do with incorrect or inconsistent motor behavior with speed_sp values less than but close to the value of max_sp (which I believe is 1050 for the large motor). For example speed_sp=1000 can cause problems (this appears to happen when stop_action is 'coast' or 'brake' but not when it is 'hold'.)

UPDATE: I left this comment without testing the motor and now that I have tested it I conclude that problem number 1 in my first message has not yet been solved, but problem number 2 has indeed been solved. Please see my next message..

ndward commented 7 years ago

With kind help from @wasabifan, I downloaded 19-ev3dev and can confirm that the problem of the motor failing to respond reliably with values of speed_sp above 950 for the large motor seems to have been solved, at least up to speed_sp = 1000, the highest value I tested with the large motor. That still leaves the question: what should be considered the maximum usable value of speed_sp for both the large motor and the medium motor?

The other problem that I mentioned in the first message in this issue, the problem of run_timed giving rotations that are inaccurate, sometimes by more than 20%, has not been solved as far as I can tell. The following script demonstrates that with a high magnitude value of speed_sp (-600) the motor rotates significantly LESS than it should and with a low value of speed_sp (100) it rotates much MORE than it should. The script below should cause the motor to finish in its initial position but in this case the errors reinforce one another and cause the motor to end up very far from its initial position.

#!/usr/bin/env python3

from ev3dev.ev3 import *
from time import sleep

mB = LargeMotor('outB')
mB.stop_action='hold'

mB.run_timed(time_sp=600, speed_sp=-600)
sleep(2)
mB.run_timed(time_sp=3600, speed_sp=100)
'''
should turn first for 0.6 seconds * -600 deg/s = -360 deg
but only turns about -345 degrees for me

then should turn for 3.6 seconds * 100 deg/s = 360 deg
but turns more like 435 deg so nearly 25% error as before!

The wheel should finish in its initial position but the 
above errors accumulate giving a final error of about 90 degrees for me
'''
WasabiFan commented 7 years ago

what should be considered the maximum usable value of speed_sp for both the large motor and the medium motor?

I would recommend using the value of my_motor.max_speed for calculations where you're attempting to go at a percentage of maximum speed. While that speed likely is unreachable, it is a value that's automatically supplied regardless of the type of motor, making your code more portable. In reality, every motor will run at a different maximum speed; it depends on manufacturing differences and wear from use.

The following script demonstrates that with a high magnitude value of speed_sp (-600) the motor rotates significantly LESS than it should and with a low value of speed_sp (100) it rotates much MORE than it should.

I'm not going to say too much on this because I'd like to hear from @dlech however I would in general not expect the time-based mode to be reliable for distance control. If you need to go to a specific position, use the position-based command and your desired speed.

It would be nice to get some data on the actual speed of the motor (e.g. a graph); that would help explain whether the motor is chronically off-target in speed, or if it's oscillating around the speed target, or if it's an issue with starting and stopping. That might be something that I can do tomorrow.

ndward commented 7 years ago

I'm not sure it's helpful to use the value of my_motor.max_speed for calculations where you're attempting to go at a percentage of maximum speed. If I want to go as fast as possible, that should be 100%, but as you say yourself that speed (max_speed) is probably not reachable. And I suppose 98% of max_speed is not reachable either. And perhaps 96% too... As you can see, it not satisfactory not to know what is the maximum value of speed_sp that can be used safely and reliably. Of course, it would be highly convenient to use the value of 1000 as the max usable value for the large motor (max_speed gives 1050). There should be no problems using speed_sp = 1000, should there? For the medium motor the maximum usable speed_sp seems to be about 1450. Can you confirm this? It is NOT ok to use a value of 1500 according to my tests (of months ago). Max_speed returns 1560.

WasabiFan commented 7 years ago

From my previous tests, I don't think that a margin of 5% is significant; remember, every motor has a different "maximum" speed. However, your point entirely valid. @dlech what do you think of lowering the max speed values to be a bit lower than what we empirically determine to be the fastest a motor can go? That would ensure that everyone can achieve that speed. However, that conflicts with the current usage of that attribute: it is theoretically the maximum speed that the attribute will accept. Why do you constrain it? What's the harm in allowing someone to try for something faster than they can actually achieve?

dlech commented 7 years ago

what do you think of lowering the max speed values to be a bit lower than what we empirically determine to be the fastest a motor can go?

I have already said what I think at https://github.com/rhempel/ev3dev-lang-python/issues/220#issuecomment-249761040. The max speeds are already set to the empirically determined speed with no load at 9V. After quite a bit of discussion several years ago, this is what @rhempel and I settled on because it really is the maximum speed possible and is an easily measurable benchmark for future motors.

If you want to translate speed to % of max speed obtainable under some other conditions, I would suggest making a function that scales 100% to max_speed * 0.7 or something like that rather than changing the drivers.

What's the harm in allowing someone to try for something faster than they can actually achieve?

There is no harm really. But since it is physically impossible to go faster, there is no harm in constraining either.

dlech commented 7 years ago

mB.stop_action='hold' mB.run_timed(time_sp=600, speed_sp=-600) ... should turn first for 0.6 seconds * -600 deg/s = -360 deg but only turns about -345 degrees for me

That makes sense if you think about the physics of what is going on. When run_timed() is called, the motor has a speed of 0. The timer starts immediately, but it takes the motor a finite amount of time to get up to a speed of 600 deg/s because of inertia.

Here is a simple test...

$ echo 600 > speed_sp
$ echo 600 > time_sp
$ echo run-timed > command; while true; do cat speed; done
318
564
622
629
622
608
589
596
602
611
587
598
312
-199
-312
62
110
-61
-50
31
27
-22
-15
0
0
0
0
0

I didn't do time stamps, but I will guess, based on when the motor started slowing down, that each cycle of the loop took about 0.05s. So, suppose it took exactly 0.1s to get up to 600 deg/s. We can model it like this...

graph

With the speed ramping from 0 to 600 deg/sec during the first 0.1s and then running at a constant speed of 600 deg/s for 0.5s, for a total of 0.6s, I calculate that it would move 330 deg, which sounds reasonably close to the observed 345 deg.

The position holding PID of the stop action causes the oscillation of the speed when the time expires, but the PID should bring the motor position back to the exact position it was the the timer ended, so we can ignore it as far as the calculations above are concerned.

dlech commented 7 years ago

Tip: If you want more accurate rotations based on run-timed, enable ramping to ramp the motor up and down in a controlled manner. This should give you something closer to the expected results. See https://github.com/ev3dev/ev3dev/issues/502#issuecomment-171534116.

dlech commented 7 years ago

mB.run_timed(time_sp=3600, speed_sp=100) ... then should turn for 3.6 seconds * 100 deg/s = 360 deg but turns more like 435 deg so nearly 25% error as before!

Repeating the experiment as before with the new parameters...

$ echo 3600 > time_sp 
$ echo 100 > speed_sp 
$ echo run-timed > command; while true; do cat speed; done
0
120
91
126
115
113
84
127
88
112
93
100
122
84
94
120
84
92
81
91
122
78
95
122
124
86
95
117
125
91
100
86
91
98
119
84
100
115
81
89
126
119
119
81
93
93
98
86
92
121
80
94
83
86
94
116
82
92
93
120
87
84
84
85
92
120
82
90
114
82
129
83
82
99
118
62
-50
-30
19
13
0
0
0
0
0
0
0
0
0
0
0
0
0
0
^C

As you can see, it has a hard time maintaining a constant speed at this low of a rate. It varies something like +/- 20-30%, so it is not surprising that the end position is off by a similar factor. See https://github.com/ev3dev/ev3dev/issues/795#issuecomment-278531204.

ndward commented 7 years ago

Here is a modified version of my script that tests speed_sp values from 50 to 1000 in steps of 50, adjusting time_sp values so that the motors should always turn 360°

#!/usr/bin/env python3

from ev3dev.ev3 import *
from time import sleep

mB = LargeMotor('outB')
mB.stop_action='hold'

for i in range(50, 1050, 50):
    mB.position=0
    mB.run_timed(time_sp=(360000/i), speed_sp=i)
    mB.wait_while('running')
    print('speed_sp = ' + str(mB.speed_sp) + ', angle = ' + str(mB.position))
    sleep(1)

Here are my results: speed_sp = 50, angle = 442 speed_sp = 100, angle = 429 speed_sp = 150, angle = 408 speed_sp = 200, angle = 392 speed_sp = 250, angle = 373 speed_sp = 300, angle = 360 speed_sp = 350, angle = 353 speed_sp = 400, angle = 358 speed_sp = 450, angle = 349 speed_sp = 500, angle = 349 speed_sp = 550, angle = 350 speed_sp = 600, angle = 344 speed_sp = 650, angle = 343 speed_sp = 700, angle = 341 speed_sp = 750, angle = 337 speed_sp = 800, angle = 325 speed_sp = 850, angle = 325 speed_sp = 900, angle = 305 speed_sp = 950, angle = 295 speed_sp = 1000, angle = 276

I note that the motor turned further than requested with speed_sp values less than 300. For example, with speed_sp=200 the motor turned 392°, which is 9% more than requested. The point is that this is a significant error but the speed_sp value is not particularly small. Also, I repeated the test with time_sp ten times bigger, thereby requesting a rotation of 3600° each time. With speed_sp = 200 I then got 3997° which is an even bigger error than before (11%), perhaps because for longer runs the inertia effect at the beginning is less significant.

dlech commented 7 years ago

Here is a modified version of my script that tests speed_sp values from 50 to 1000 in steps of 50, adjusting time_sp values so that the motors should always turn 360°

This is not exactly correct. It is really "so that the motors should always turn less than 360°". Did you read/understand my explanation of the physics in https://github.com/rhempel/ev3dev-lang-python/issues/220#issuecomment-278835044?

And I already offered a tip on how to get around the physics to get the expected results. Here is a modified script.

#!/usr/bin/env python3

from ev3dev.ev3 import *
from time import sleep

mB = LargeMotor('outB')
mB.stop_action='hold'
mB.ramp_up_sp=100
mB.ramp_down_sp=100

for i in range(50, 1050, 50):
    mB.position=0
    mB.run_timed(time_sp=(360000/i), speed_sp=i)
    mB.wait_while('running')
    print('speed_sp = ' + str(mB.speed_sp) + ', angle = ' + str(mB.position))
    sleep(1)

And the results:

speed_sp = 50, angle = 444
speed_sp = 100, angle = 434
speed_sp = 150, angle = 415
speed_sp = 200, angle = 399
speed_sp = 250, angle = 380
speed_sp = 300, angle = 368
speed_sp = 350, angle = 364
speed_sp = 400, angle = 363
speed_sp = 450, angle = 363
speed_sp = 500, angle = 363
speed_sp = 550, angle = 366
speed_sp = 600, angle = 363
speed_sp = 650, angle = 372
speed_sp = 700, angle = 371
speed_sp = 750, angle = 366
speed_sp = 800, angle = 360
speed_sp = 850, angle = 362
speed_sp = 900, angle = 348
speed_sp = 950, angle = 328
speed_sp = 1000, angle = 310

At low speeds, we still have an issue, but we understand that we don't have accurate speed control at low speeds in the motor driver, so no surprise there. From 350 to 850, you can see we get the expected results with a small margin of error. Once we hit 900, the physics of the motor is limiting us again. The motor cannot get from 0 to 900 in the 100ms specified by the ramp_up_sp. The higher overshoot around 700 is probably because we can't slow down the motor quite as fast as the ramp_down_sp specifies. If we make the ramp time long enough you should see the expected results all the way to the highest speed.

ndward commented 7 years ago

Ramping may be helpful for advanced users but is probably not appropriate for beginner EV3 Python programmers.

I feel this issue is coming to a close. To summarise:

For HIGH values of speed_sp, the actual average speed tends to be LESS THAN the requested speed (speed_sp), at least when the motor starts from rest. The difference can exceed 20% when the motor turns only for a short time. The difference is no doubt due mainly to the inertia of the motor which means that for the first fraction of a second the motor will not have had time to reach the requested speed. Having said that, the tables below suggest that the actual speed achieved by the large motor with speed_sp=1000 is essentially the same as that achieved with speed_sp=950, which suggests that the large motor is not really capable of achieving speeds above 950°/s. My experiments with EV3-G suggest that a large motor power value of 100 in EV3-G corresponds to a speed of about 925°/s, suggesting that the Lego engineers have determined that this is the maximum speed that can be reliably achieved by the large motor.

For LOW values of speed_sp, the actual average speed tends to be GREATER THAN the requested speed (speed_sp). The difference can exceed 20%. The discrepancies are probably due to the difficulty of accurately measuring motor speeds over very short time intervals, though this may not explain why the average speed for low values of speed_sp is systematically HIGHER than requested. Thanks to @dlech for identifying the probable causes of the discrepancies for high and low values of speed_sp.

Here is the latest version of my speed testing script, with the results. The script calculates the actual average speed of a lightly loaded large motor over a 3s interval (requested explicitly or implicitly) for both the run_timed() and run_to_rel_pos() functions for a range of speed_sp values between 50 and 1000.

#!/usr/bin/env python3
from ev3dev.ev3 import *
from time import sleep, time
mB = LargeMotor('outB')
mB.stop_action='hold'

duration=3
print()
print('run_timed() always for '+str(duration) +'s')
print('speed_sp, real av speed, difference')
for i in range(50, 1050, 50):
    mB.position=0
    mB.run_timed(time_sp=duration*1000, speed_sp=i)
    mB.wait_while('running')
    realspeed=round(mB.position/duration)
    print(i, realspeed, str(round(100*(realspeed-i)/i,1))+'%')
    sleep(1)

print()
print('run_to_rel_pos(), always implicitly requesting '+str(duration) +'s')
print('speed_sp, real av speed, difference')
for i in range(50, 1050, 50):
    starttime=time()
    mB.run_to_rel_pos(position_sp=duration*i, speed_sp=i)
    mB.wait_while('running')
    realspeed=round(duration*i/(time()-starttime))
    print(i, realspeed, str(round(100*(realspeed-i)/i,1))+'%')
    sleep(1)

run_timed() always for 3s speed_sp, real av speed, difference 50 60 20.0% 100 119 19.0% 150 173 15.3% 200 221 10.5% 250 263 5.2% 300 305 1.7% 350 350 0.0% 400 398 -0.5% 450 449 -0.2% 500 498 -0.4% 550 548 -0.4% 600 596 -0.7% 650 646 -0.6% 700 694 -0.9% 750 743 -0.9% 800 791 -1.1% 850 838 -1.4% 900 886 -1.6% 950 930 -2.1% 1000 934 -6.6%

run_to_rel_pos(), always implicitly requesting 3s speed_sp, real av speed, difference 50 61 22.0% 100 119 19.0% 150 171 14.0% 200 220 10.0% 250 261 4.4% 300 303 1.0% 350 348 -0.6% 400 396 -1.0% 450 446 -0.9% 500 495 -1.0% 550 545 -0.9% 600 594 -1.0% 650 642 -1.2% 700 691 -1.3% 750 737 -1.7% 800 785 -1.9% 850 833 -2.0% 900 880 -2.2% 950 925 -2.6% 1000 930 -7.0%

This discussion has made me realise how important it is for a rover's motors to be synchronised so as to have a constant and correct speed ratio, something that is currently possible in most EV3-G compatible languages including EV3-G and EV3 Basic but NOT possible with Ev3dev, I believe. In ev3dev when the rover is instructed to follow a curved path with one wheel having a high speed_sp value and the other having a low value, the fact that one wheel will turn too fast while the other is turning too slowly will cause the robot to follow a path significantly different form the one requested. This problem is avoided when the motors are synchronised such that the motors keep the correct speed ratio. I have opened an EV3dev issue 853 https://github.com/ev3dev/ev3dev/issues/853 to request that a function should be added to EV3dev/ev3 Python that would allow for motor synchronisation such that two motors can maintain a constant and correct speed ratio.

UPDATE regarding the maximum usable speed for the EV3 motors. I came across official Lego documentation at http://gillespie.agrilife.org/files/2015/04/EV3-Motors-Sensors-Explained.pdf which states that 'The large motor runs at 160-170 rpm (960-1020°/s). The medium motor runs at 240-250 rpm (1440-1500°/s)'. This would imply that a speed of 1000°/s cannot be reliably obtained by EV3 large motors.

I came across some pages which indicate that LeJos uses a maximum speed of 900°/s for the EV3 large motor. For example on https://lejos.sourceforge.io/forum/viewtopic.php?t=7268 gloomyandysays ' The actual max speed of the motor is dependent on the battery voltage and the individual motor, 900 degrees/sec is a rough guide for the large motor. Note that if you try and set a speed that is not sustainable by the motor/voltage then regulation will fail.' LeJos may also have the possibility of requesting speeds as a percentage of the maximum possible speed, like EV3-G?

I came across an extract from The art of Lego Mindstorms EV3 Programming https://books.google.co.uk/books?id=cCABBQAAQBAJ&pg=PA213&lpg=PA213&dq=ev3+large+motor+max+speed+degrees+per+second which indicates that for EV3-G power (speed) setting up to about 75 the setting can be multiplied by 10 to get the corresponding speed in degrees/s. E.g. a power setting of 50 corresponds to 500°/s. Then it says that for power values above 75, 'higher Power values don't move the motor any faster – a condition known as saturation. So in practice there is no difference between setting the Power parameter to 80 or 100 – both settings make the motor go about 750 degrees per second.' This actually does not seem plausible to me – my own tests seem to indicate that a power value of 100 can correspond to about 925°/s for a lightly loaded large motor running for several seconds so that inertia (resistance to acceleration) is not a factor.

My conclusion: the maximum usable speed for an EV3 large motor should be assumed to be 900°/s.

dwalton76 commented 7 years ago

Anything to do in this one or can we close its?