When I visited Akihabara last Saturday with Inoue-san, I picked up several different types of LEDs and phototransistors to experiment with. The initial challenge was to hack together a way for Maxwell to test the linearity of his servo motors. Later the knowledge gained will be useful in developing an encoder strategy and for other applications.
The first approach used a discrete LED and a photodiode. It was workable, but maintaining the physical positioning of the components and aligning them with the servo wheel marking turned out to be the big challenge. The second approach used a packaged LED/phototransistor pair.A small opaque 'flag' was fabricated from tape and attached to the side of the wheel, then trimmed so that it passed through the center of the detector.
A simple circuit was breadboarded on Maxwell's BOE -
Note: D1 and Q1 share the same packaging with a small slot for the flag to pass between them interrupting the optical path.
The circuit output signal was connected to IN3 on the Basic Stamp controller. A test program was written and debugged to cycle through a range of PULSOUT values measuring the number of commands required to rotate the servo motor 360 degrees. Since the flag position was unknown at the beginning of each cycle, the program had to reposition it automatically. An unexpected benefit of this approach was that the servo was already in motion at the start of each measurement. During the previous tests the servo was always at rest at the start of each test cycle. This meant that the servo had to overcome some initial inertia and accelerate to the test speed during the test itself. The impact would be minor for low rotational speeds, but would rapidly impact the overall measurements as the speed increased. Having the servo already rotating at the speed under test is a more accurate test.
At the completion of each cycle, the program outputs the measurements via the DEBUG console where they can be copied and moved to Excel for analysis. The analysis process can be simplified quite a bit if the program outputs the data in a pre-processed form. For example, if it writes the data to the DEBUG console in comma delimited format, it's easy to just cut and paste into Excel without having to reformat and/or edit the data.
This chart shows the test results for Maxwell's right servo motor when tested with PULSOUT values ranging from 550 to 740 in increments of 5 -
The automated testing provides much more consistent results as well as allowing for improved measurement granularity. For example, the earlier tests were run manually with test increments of 10. The initial automated test increment was 5, but could be as low as 1. One concern is that for fast rotational speeds the number of PULSOUT commands sent is relatively small - in the range of 70 or so per 360 degree rotation. To improve the measurement granularity the program can be modified to measure the number of commands required for mulitple rotations.
The biggest single advantage? Simple - I can start the testing, then go do something else. I don't have to babysit it. And, it allows me to run longer, more extensive testing than the manual approach.
In thinking through the servo motor programing strategy I realized that the servos may be non-linear. Research (via Google, of course) uncovered a simple Basic Stamp test program in the BOE-BOT guide to robotics on the Parallax website. Maxwell isn't a BOE-BOT, but is close enough that the programs run with very few modifications.
The basic approach is to put the controller into a loop where the timing is known then measure how far the servo rotates during the loop execution, then multiply the rotation by an adjustment factor. In this particular case the program rotates the servo for six seconds while the rotations are counted. The result is then muliplied by a factor of 10 to derive the RPM figure. Their recommendation was to use PULSOUT commands ranging between 650 to 850, but I decided to take a couple of additional measurements extending the range to see how quickly it would become non-linear.
This chart shows the test data for one of Maxwell's servos. The vertical axis represents the PULSOUT parameter passed to the servo while the horizontal axis charts the resulting RPM. It's easy to see that the servo zero is slightly offset from the theoretical null position. If it was zero'ed perfectly the curve would in intersect the origin at (0,750). The offset is relatively minor - small enough that a constant in the control program can adjust for it. The servo is relatively linear from 650 to 850, but rapidly becomes non-linear outside of that range.
The next steps are to -
- Run the same linearity test on the left servo
- Compare the linearity and zero offsets between the two
- Develop a more sophisticated test program to automatically collect the data with more precision and granularity
#1 will happen some time over the next few days. My schedule is pretty full, but the test only takes about 30 minutes to run per servo, so I can fit it in somehow.
#2 involves some analysis of the data in Excel and may be as simple as setting up a couple of additional data columns that add offsets to the collected data then chart the results.
#3 is simple in principle, but may end up presenting some difficult and interesting challenges. My initial thought is to breadboard a sensor similar to those used for line tracing. Position the sensor over the wheel attached to the servo to be tested. The wheel surfaces are a very flat matte black, so attaching a piece of light colored Post'it note may be enough to provide a leading edge to trigger the sensor.
The test program should look something like this:
Reset the test data array and counters
' Cycle through the range of PULSOUT values to be tested
For range = low_value to high_value
' move the wheel into test position
Zero the wheel
Reset the time counter
Send a PULSOUT command to the servo
' in order to keep the timing constant, calculate the complement of
' the test value and send that to the other servo so that the elapsed
' time for both servos always totals to the same value
Send a complementary PULSOUT command to the other servo
Increment the time counter
Check the sensor
If the wheel has finished one rotation then update_data
' as the values approach and pass through the zero movement point
' (around 750) the servo will move extremely slowly or not at all.
' In those cases this loop should time out.
If time_counter = time_out then goto update_data
Store the time counter value in the test data array
The advantages of this approach are-
- Higher precision
- Automated process
- Repeatability - a major factor should it become necessary to change servos later
The primary disadvantage is -
- Test times may turn out to be very long
Note: The Parallax BOE-BOT guide is available on their website and can be downloaded free of charge. I haven't studied it in depth yet, but a quick read-through indicates that there are lots of helpful hints and good information for anyone starting out in robotics.
It feels pretty good to have Maxwell up, communicating, and actually moving around - even if he does hiccup from time to time. After my last post, I spent some time improving his motion, and cleaning up my code. After about an hour of tuning, he now makes repeatable moves, right angle (or really close to right angle) turns, and can spin in circles.
Experimentation with the program/servo timing highlighted the need to spend some time focused on two inter-related challenges-
- motor timing/control
Maxwell's design is basically a symmetrical "wheelchair" model with the motion provided by two servos that have been modified for continuous rotation. He is relatively light - especially since I am haven't mounted a battery pack on his chassis yet and am still running him from a wall wart. But, as light as he is, the basic laws of physics still apply. It's really obvious from the testing and examining the videos that momentum/inertia is a factor that can not be realistically ignored.
To move Maxwell forward the program sends a series of balanced PULSOUT commands to the drive servos. Although the servos are mounted along the same axis, the orientation of their cases and drive shafts are exactly opposite each other. They have to turn in opposition (one clockwise and the other counterclockwise) at equal rates for Maxwell to move forward in a straight line. And, since their zero set values are slightly different, the values the program sends need to be offset to compensate for the difference. This is a simple calculation, easy to build into the program at a low level so that we don't have to take it into consideration later.
When Maxwell is 'at rest' - i.e. stationary, assuming that there are no external forces working on him, he is 'stable.' When the program generates a forward move from his stable state, he appears to start moving in a clean fashion and travels forward in a straight line.
The initial program ignored inertia. That approach worked fine for the initial move, but ran into problems as soon as Maxwell needed to stop. His mass is high enough that the friction/braking inherent in the servos isn't enough to stop him quickly. Testing revealed a small but not insignificant amount of overshoot.
In actual operation Maxwell will be reacting to his surrounding environment and changing course accordingly, so in most cases it might be acceptable to ignore this type of small error. Still, it would be best if his 'dead-reckoning moves match his real world moves as much as possible.
Quite a few of the competitors in the All Japan Micromouse competition had problems with dead-reckoning. They started off fine, but after a lot of traveling, turns, and retracing, they got confused about their orientation and sometimes ran into the maze wall. In order to re-establish their orientation some of them used a special move sequence that involved backing into a maze wall several times. This worked only because their rear surfaces were flat, and because hitting the wall in reverse isn't considered to be a 'touch'. I was told that a Korean competitor came up with this creative strategy several years ago. While that approach is certainly workable, it doesn't strike me as being a good engineering solution to the problem. It works for the Micromouse competition, but it probably wouldn't be workable in other contests or for exploring the real world outside the maze. Besides, I can't imagine trying to explain to my wife why Maxwell needs to hit his butt on the wall....
First, improve his movements as much as possible, then implement a sensor based orientation strategy.
When Maxwell needs to turn the program sends the servos another set of matched PULSOUT commands, only this time they are instructed to turn the same way - i.e. both clockwise or both counterclockwise. Assuming that both servos are fairly closely matched, and that there are no wheel traction issues, Maxwell should be able to rotate smoothly around his own centerpoint.
What really happens, however, is a little thing called rotational inertia...
Maxwell continues to turn slightly after the program thinks he has already stopped. Of course, the program could be modified to take the overshoot into account - perhaps by the addition of a small fudge factor. Unfortunately fudge factors have a bad habit of melting into sticky goo after a while.
If it's at all possible, it would be much better to figure out an approach that eliminates the inertial overshoot. This will probably involve using proportional or ramped signals to the servos to handle accelleration and decelleration as a part of the movement sequences.
Going back to the issue of dead reckoning, one of the programs to test Maxwell required him to run around a square.
The sequence was forward, turn left, forward, turn left, forward, turn left, ... etc. until he completed the square. If he executed everything perfectly he would end up exactly where he started with the same orientation.
He ran the first leg fine but overshot the end slightly. The first turn was a little more than 90 degrees - not much, but enough to be noticeable. Naturally, the same errors continued and accumulated as he completed each leg of the square.
I haven't tried it yet, but it's easy to imagine what would happen if I put him into a long loop running around the course-
Fantastic, if you're building a Spirograph... Not so fantastic if you're building a working robot...
There are several reasons why a better motor timing/control approach seems to be necessary. First, given the discussion above, the program control design needs to be capable of managing the servo motor accelleration and decelleration.
Second, everything I've read on the internet and in books recommends that servos work better if they receive a fairly constant control signal stream. If they go for a while without seeing a control pulse then they stop actively controlling the motor position. It may be a good design approach to send the servos control signals, even when no movement is required. Early in Maxwell's testing he would perform well but infrequently, almost at random, he would hiccup. Instead of rapidly spinning his wheels in the direction I expected, he would turn them slowly, sometimes in the opposite direction. However, when I modified the driver program so that the servos were always being sent a control signal, his errant behavior disappeared.
Third, and possibly more important, servos weren't designed for continuous use as drive motors. It seems to be common for servos used as robot motors to be pushing up daisies after about 100 hours of use. If the motor control program can treat the servos in a 'kinder, gentler' fashion, then they will probably live longer - and give the robot's owner fewer ulcers.
Maxwell's first driver programs were very binary. They used a PULSOUT using 500, 750, and 1000 values to command the servo to turn CCW, stop, or turn CW. While it worked, and Maxwell scooted around the desktop, it was obviously pretty hard on the servos. The servos made a lot of crunching noise, and the power LED on Maxwell's brain board would dim. A normal servo in an R/C or model plane application might react like this-
The green square wave represents the request from the control program (not the actual control signals that are generated.) For example, the operator throws his R/C model car joystick hard over to the right. The unmodified servo control circuitry generates an error signal and drives the motor until it gets the error signal back to zero. The red line above represents the relative servo position as it responds to the request. Its motor is only drawing significant current when the error signal is non-zero. Overlaying the two curves and looking at the area between them (red/yellow bottom curve) we can get a rough feel for how they interact, and how much power is pumping through the servo.
When the servos are modified for use as continuous drive motors their internal feedback link is deliberately disabled, and we replace it with software program control. If we just drive the servo in a simple fashion - full forward, stop, full reverse, then we're probably giving it a huge amount of stress and decreasing it's useful life. On the other hand, if the program can be designed to implement accelleration and decelleration curves, the servos will probably last longer. At least that's my current thinking.
I put together a simple FOR ... NEXT loop to test the servo zero values. The first time around the loop range was from 700 to 800 with increments of 5. Every time it went through the loop it would send each servo a set of 50 PULSOUT signals with the current value, and display the value on the DEBUG console. Although I could see when the servo stopped moving, I found it was much more precise if I let my fingertip brush the wheel. Even the slightest motion was easy to detect using that technique.
Testing showed that the right servo stopped moving when the value reached 740 and started to reverse at 750. The same values for the left servo were 735 and 745. From this data it was pretty evident that both the servo's offsets from 750 and the difference in zero values between the two were contributing to the position and orientation drift.
The FOR ... NEXT loop was modified to range from 720 to 780 with increments of 1, and the test was repeated. The refined values for the right servo were 738 and 742. The corresponding values for the left servo were 737 and 739. I decided to use the mid-point for each servo, and modified the original program to reflect those values. Since both of the servos exhibited a slightly negative zero offset from 750, the forward and backward values were adjusted accordingly.
I reran the previous day's test program with the new values, and it worked great. There was still some relative position shifting, but very little. The next step was to get the robot to do something more useful. The first test was a rectangle move. Maxwell would move forward, turn CCW, move forward, turn CW, move backward, turn CCW, move backward, turn CW. That should bring him back to where he started.
The program was setup with several constants containing all the parameters in one, easy to change/adjust, place in the program. Maxwell, and the program, worked great. Nevertheless, I still have a lot of work to do to refine his turns. On the straight away his movement is almost perfect. But when he has to make a turn it's more difficult to get him to stop right at 90 degrees.
And, for some strange reason, every once in a great while, all of a sudden he decides to turn the opposite way from what I expected. This may be caused by the servo offset software approach I used. In any case, he's up and running around, not in circles but in a small rectangle on my desk.
Download rectangle.wmv (152 kb.)
Maxwell's performance during the movement cycling test improved a lot with the addition of the 'fullstop' subroutine. After executing 5 iterations of the movement cycle his longitudinal axis alignment was within 5 degrees of where it started, but the X, Y positioning of his center was still offset by roughly the same distance as the earlier tests. It was a huge improvement, no question, but there are still some underlying problems to deal with.
In order to sort out what might be happening, I propped Maxwell up on some spare batteries (they happened to be just the right height to get his wheels off the ground, and they were handy), drew a reference line on a piece of Post-It note, setup my digital camera to video the test, and then put him through his paces.
It became immediately obvious that the servo was creeping when it should have been motionless. A quick check showed that the other servo was exhibiting the same behavior, though the magnitude was considerably different. A little bit of internet surfing revealed that I should have zeroed the servos before I installed them into Maxwell's chassis....
Download 041128_servo_zero.wmv (100 kb)
Oh well, live and learn. Now I have two choices (at least two, there may be more.) I can disassemble the robot and zero the servos, then reassemble, or I can zero them in software. My immediate choice, perhaps wrong for the long term, is to zero them in the software. I suspect that the zero setting will change over time as the servo wears, or anytime I have to change a servo. My rationale is that if I build zero offsets into my code as defined constants, then it will be easy to change them universally later if that becomes necessary. Designing a program to determine the zero settings should be very straight forward, though I have some concerns about the possible effects of hysteresis on the zero settings.
Another 'learning' from my experience with Maxwell so far is that I will have to start labeling everything - i.e. putting small tags on the servos, cables, etc. and documenting it in my logs, photos, and videos. It's already become important to keep track of which servo is which. Later, as I start adding sensors and other circuitry, it will become even more important.
After I actually managed to get Maxwell, my first robot, to move around under his own power, I wanted to take a look at how repeatable his movements are. My earlier program was setup to have Maxwell move about one robot length forward; reverse back by the same amount; rotate 90 degrees CCW; then rotate 90 degrees CW. If Maxwell's servos were perfect he would end up in exactly the same place and orientation. It was obvious from watching him move around, that he was far, very far, from perfect.
In order to get a handle on what was happening, I put my camera tripod on top of my desk, and used my digital camera to video his movements. The camera is a Sony F-717 designed for straight photography. I wouldn't use it as a video camera replacement, but its quality turned out to be more than sufficient to handle this type of project.
To exaggerate any errors so that they would be easier to analyze, I rewrote the program so that Maxwell repeats the same move sequence five times in a row. Video from several test runs showed that although the robot was making linear moves in a straight line, it was definitely having problems maintaining a repeatable course.
This chart shows the robot's original position in red, and it's final position after five repetitions in black. Each time it went through the cycle its ending position was offset by a small factor on an X, Y basis, and by a large factor rotationally. By the time it had completed the 5 move cycles, Maxwell's longitudinal axis had rotated by approximately 135 degrees. There was also some small X and Y displacement of his center - but I'm going to ignore that for the moment until I get the bigger problems sorted out. It may turn out to be a side effect, and I don't want to waste a lot of time chasing that particular rabbit until I have to.
Examining the video in more detail it appeared that the biggest single problem was overshooting due to inertia. Once the robot was moving, it tended to continue moving even after the program had stopped telling the servos to move. The robot's momentum was hard to slow down purely by the friction generated within the servos.
To test this hypothesis, the program was modified to call the 'fullstop' subroutine after each move. The Fullstop subroutine sends series of PULSOUT commands to the servos with a value of 750, which should be zero rotation. The modification appears to correct roughly 80-90% of the problem, but there are obviously other contributing factors. I'll have to dig deeper.
Download servo_test_0004.wmv (video - 300 kb.)