by PaulL » Sat Sep 11, 2010 4:15 pm
by PaulL
Sat Sep 11, 2010 4:15 pm
I received a PM with questions about this, so I figured I'd take a moment and write up a bit more about movement and what the code I posted in this thread does, how, and why.
In working with RoboIO, I realized early on that the code (servo moves, in particular) isn't really built for multitasking / multithreading. The problem I sought to get away from in using the MRC-3024 in my Robonova (interrupting a move, responding to things in the environment) persisted with using the RoboIO code with my Roboard. You can do fancy things in C on an MRC-3024 to get past the limitations of RoboBasic, but that wasn't my ideal approach, and I wanted much more processing power.
Physically, the Roboard is capable of exactly what I want and more, but not when using the RoboIO RCServo code as intended. I could manipulate the PWM module and abandon the RCSERVO lib to get similar results, but I found it easier for me to rewrite my own "RoboIO" code. Up to now, I am glad that I did, it's given me significant knowledge of how the board functions.
I have rewritten most of the capability found in RoboIO, for a few different reasons, here are the main ones:
* To understand Roboard better and know what the hardware can actually do.
* To simplify register access and control.
* To have a multithreading-friendly means of control.
This included writing my own servo-move code, which I am still working on (I have almost all of the work done, but am currently side-tracked with the sound card issue as in my other thread). Specific to the servo functions, I wanted to be able to control the servos better than just start here / stop there.
What is a movement in a servo, exactly? A move is just changes in position over time.
Time is one of the more problematic aspects of programming under Windows. Windows was built for people, not machines. A user won't notice a 100 mS lag, but your 'bot will! You can make Windows work with machines, but it takes some creativity to do so. Bear in mind, I'm not talking about Windows CE (predictive code execution and more), I'm talking about Windows XP and the rest. The timers in Windows are horrible for controlling machines. The Windows Forms timer is the worst- it may fire, it might wait a while. At best, it's got a 15 mS interval. A much better timer is the Multimedia timer, but it also is not perfect and can be ignored by the operating system for some period of time (not as bad as other timers, though). Timers in Windows, in short, are not absolutely periodic, and you absolutely can not use them as a time base in and of themselves. Even retrieving system time is problematic.
If you can't count on a Timer for keeping time, what can you do? I opted for the most accurate method, accessing a core CPU function called RDTSC. RDTSC is a function for retrieving the value for the number of CPU clock ticks since power-up. Time won't get more accurate than that on a desktop PC.
RDTSC doesn't give you a Timer event, but with a useable timer like the Multimedia timer coupled with RDTSC to tell how much time has truly elapsed, you can get very respectable results. You can even tune those results with the MM Timer's interval to back off timer resolution to an acceptable level (you wouldn't want to update the timer so fast that the value doesn't really change from one update to the next!).
Regarding servo movements being changes in position over time, this view can be boiled down to its key components: Time means duration, as a move will start at some time, and end at a later time. Duration = End Time - Start Time. Time is pulled from RDTSC, with each increment being a tick of the CPU's clock.
Some might think, "I am just moving a servo, why do I care so much about time, why is it so critical?"- Well, because timing is everything.
Seriously, Time is a defining characteristic of a physical movement. If it wasn't, we'd just say "Set servo to position X", and the servo would get there as fast as it could. Try to make your bot walk with THAT logic.
Instead, we have to massage motion out of the servo to get TO some position at some point in time. If we don't do that accurately enough, there will be jerkiness and fluid motion will be ruined, likely with random interruptions of the move as it occurs.
So, in my code, the Timer just ticks off at pseudo-random intervals (due to Windows), and in the Timer event, I call a function to get RDTSC to calculate FractionOfMove for my move code. When I say "Ticks", I'm referring to the value of RDTSC here. The resulting "FractionOfMove" calculation is (CurrentTicks - StartTicks) / (EndTicks - StartTicks), resulting in a value representing the fraction of the move. If CurrentTicks >= EndTicks, then the move is over, and we just set the current position to the end position and end the move. FractionOfMove transitions from 0 to 1 fractionally over the course of the move. Zero at start, 1 at end of move.
All of that gives us the ability to know time (in CPU clock ticks) and an event in which to update servo positions (Multimedia Timer), now we just need to set the position of the move itself for a given fraction of the move. For a point-to-point, no acceleration move, this would mean something like CurrentPosition = ((EndPosition-StartPosition) * FractionOfMove) + StartPosition. This would give you the same result as RCServo, and same as stock MRC-3024 moves, and many others.
Doing what I often do, I'm going to put in a few notes: I played with the math Sine function for acceleration / deceleration, but found it to be problematic when interrupting a move. It was also more CPU-intensive. I also went after the true calculations for the physics of trajectories and acceleration, which although was an interesting dive into calculus (not my strongest area), also resulted in CPU-intensive code. The Calculus-oriented physics calculations resulted in wide-swinging waves, often overshooting position and reversing when things (duration or end position) changed during the move.
I thought about it, a lot. I wanted simple calculations without all the CPU use and fuss. I started just looking at Acceleration. I ended up with Acceleration as FractionOfMove * Distance * FractionOfMove (note, "linear" movement is simply FractionOfMove * Distance). This gives a nice acceleration curve. At the start of the move, distance will be zero (0 * Distance * 0). At the end of the move, the result is the distance (1 * Distance * 1). The result inbetween is a nice acceleration curve. I used this same principle for decel, accelerating to .5 of the move as the end point, and reversing the values for decel as in the code. This worked nicely.
Now, on to interrupting a move:
Because by nature a timer is threaded, it can also be interrupted, and things can change that affect what happens in the timer external of the timer's code. This is bad for movement, but we've already dealt with that. It is GOOD for being able to alter what the timer does.
We still have a problem, if anyone's paying attention.
We can't directly change the end time or the end position without grossly affecting motion. The servo will suddenly feel the need to be somewhere else if end time or end position is changed during the move, and that's bad.
What to do, what to do? We change our destination GRADUALLY. This is speaking of code I haven't yet written (mainly due to focusing on the sound card problem as per my other thread here), so I'm going to throw out some ideas in my head that I haven't coded yet:
* Calculate the difference in the new end position / end time versus current, and use my 0,1,2 theory to apply the change. This theory basically means that for a change's net value to be a factor of 1 (itself), it must transition from factors 0 to 2 fractionally over time so as not to be abrupt. In code, this would mean from the time the change is made to a new end time, the end time would be altered very gradually, then more and more as the end time nears. This could mean ending sooner or later. Imagine drawing out a deceleration, or shortening up a deceleration. Your position at time of change would be offset by 0 * change, at the midpoint from start of change to end of move by 1 * change, and at the end, offset by 2 * change. Same is true for a position being changed. The problem I see here is the possibility for abrupt changes towards the end of the move if the intended change is drastic- but then, if a change is drastic, wouldn't the resulting movement be equally drastic?
* Do something creative with the last move's delta (position over time) and the already written accel / decel code. Not sure how much trouble this will cause, I will end up experimenting a bit with this one.
So there we go, my $.02 on motion and servos.
*** General Update ***
I have been working on board design for the sound card for Roboard, and am getting close to something that should work. With the costs involved, I'm very tempted NOT to implement too much on this board. Right now, I'm thinking of adding a microphone preamp, a 1 to 2 watt amplifier, and maybe a "steering" circuit to accept 2 microphone inputs and generate an analog signal relating to left-to-right panning of signal source (this would support "turning of head to listen", and would take load off CPU to do the same in code). As much as I'd like to integrate a power distribution system consisting of DC-to-DC converters with digitally tunable power output, the cost would be ridiculous in low volume, likely approaching the cost of Roboard itself (and the board would be a bit larger, and heavier, etc). That part of my project will probably have to be separate, which may work better in distributing the components around the 'bot a bit. PCB's will be a bit steep on price, and I need to buy a hot-air reflow station. Further, the board's got to be right from the start. Anyone betting on a disaster here? I almost am.
I've also repaired a few "broken tooth" HSR-5498SG servos. One little "karbonite" gear in the middle of the steel ones, and no surprise, IT is the one that fails.
Regarding the e-chain based hands, I have a couple micro servos sitting on the bench to repair, the pot leads are stressed, and they only work when magic finger pressure is applied just so. I have fine-gauge silicone wire I'm using to isolate the board from the stressed pot leads- tedious work on the micro servos. As for the hands, I have some drawings for some areas I know will not change, and I have what may be a final concept for hand bracketry sketched on paper. I need to fab some parts in polycarb, test the fit, and finalize the bracket dimensions. Then, I can CNC some brackets and build the hands.
I KNOW the hands will work out fine, the only problem I have left is to add wrist rotation in what is almost zero space. I'm not sure what I'm going to do there. I'm just going to build the hands as tight as I can and work from there. Worst case scenario, I hang a big servo out of the way and rotate the hand at a bearing joint.
Hips. I might get below the 4mm height I was thinking they were going to add. I'm going to experiment with some "Nylatron" for the thrust part of the bearing. If it doesn't stick, that's what I'm going to use. If it DOES stick, I'm going to try nylatron as a ball bearing surface, order some bearing balls, and make a ball cage out of UHMW or teflon for the thrust bearing. The problem I'm thinking I'm going to have is that the 2 x 1mm thick thrust washers and 2mm thick roller bearing assembly are just too heavy. It's a lot of weight, and they're very over-rated for this use (something like >1000 pounds thrust). They slap right together with the center bearing, but I want something lighter weight. Besides, if I cut the thrust washers myself, I can integrate a pulley on the circumference for rotating the joint (otherwise, I'd have to add it separately).
That's all for now...
Take Care,
Paul
I received a PM with questions about this, so I figured I'd take a moment and write up a bit more about movement and what the code I posted in this thread does, how, and why.
In working with RoboIO, I realized early on that the code (servo moves, in particular) isn't really built for multitasking / multithreading. The problem I sought to get away from in using the MRC-3024 in my Robonova (interrupting a move, responding to things in the environment) persisted with using the RoboIO code with my Roboard. You can do fancy things in C on an MRC-3024 to get past the limitations of RoboBasic, but that wasn't my ideal approach, and I wanted much more processing power.
Physically, the Roboard is capable of exactly what I want and more, but not when using the RoboIO RCServo code as intended. I could manipulate the PWM module and abandon the RCSERVO lib to get similar results, but I found it easier for me to rewrite my own "RoboIO" code. Up to now, I am glad that I did, it's given me significant knowledge of how the board functions.
I have rewritten most of the capability found in RoboIO, for a few different reasons, here are the main ones:
* To understand Roboard better and know what the hardware can actually do.
* To simplify register access and control.
* To have a multithreading-friendly means of control.
This included writing my own servo-move code, which I am still working on (I have almost all of the work done, but am currently side-tracked with the sound card issue as in my other thread). Specific to the servo functions, I wanted to be able to control the servos better than just start here / stop there.
What is a movement in a servo, exactly? A move is just changes in position over time.
Time is one of the more problematic aspects of programming under Windows. Windows was built for people, not machines. A user won't notice a 100 mS lag, but your 'bot will! You can make Windows work with machines, but it takes some creativity to do so. Bear in mind, I'm not talking about Windows CE (predictive code execution and more), I'm talking about Windows XP and the rest. The timers in Windows are horrible for controlling machines. The Windows Forms timer is the worst- it may fire, it might wait a while. At best, it's got a 15 mS interval. A much better timer is the Multimedia timer, but it also is not perfect and can be ignored by the operating system for some period of time (not as bad as other timers, though). Timers in Windows, in short, are not absolutely periodic, and you absolutely can not use them as a time base in and of themselves. Even retrieving system time is problematic.
If you can't count on a Timer for keeping time, what can you do? I opted for the most accurate method, accessing a core CPU function called RDTSC. RDTSC is a function for retrieving the value for the number of CPU clock ticks since power-up. Time won't get more accurate than that on a desktop PC.
RDTSC doesn't give you a Timer event, but with a useable timer like the Multimedia timer coupled with RDTSC to tell how much time has truly elapsed, you can get very respectable results. You can even tune those results with the MM Timer's interval to back off timer resolution to an acceptable level (you wouldn't want to update the timer so fast that the value doesn't really change from one update to the next!).
Regarding servo movements being changes in position over time, this view can be boiled down to its key components: Time means duration, as a move will start at some time, and end at a later time. Duration = End Time - Start Time. Time is pulled from RDTSC, with each increment being a tick of the CPU's clock.
Some might think, "I am just moving a servo, why do I care so much about time, why is it so critical?"- Well, because timing is everything.
Seriously, Time is a defining characteristic of a physical movement. If it wasn't, we'd just say "Set servo to position X", and the servo would get there as fast as it could. Try to make your bot walk with THAT logic.
Instead, we have to massage motion out of the servo to get TO some position at some point in time. If we don't do that accurately enough, there will be jerkiness and fluid motion will be ruined, likely with random interruptions of the move as it occurs.
So, in my code, the Timer just ticks off at pseudo-random intervals (due to Windows), and in the Timer event, I call a function to get RDTSC to calculate FractionOfMove for my move code. When I say "Ticks", I'm referring to the value of RDTSC here. The resulting "FractionOfMove" calculation is (CurrentTicks - StartTicks) / (EndTicks - StartTicks), resulting in a value representing the fraction of the move. If CurrentTicks >= EndTicks, then the move is over, and we just set the current position to the end position and end the move. FractionOfMove transitions from 0 to 1 fractionally over the course of the move. Zero at start, 1 at end of move.
All of that gives us the ability to know time (in CPU clock ticks) and an event in which to update servo positions (Multimedia Timer), now we just need to set the position of the move itself for a given fraction of the move. For a point-to-point, no acceleration move, this would mean something like CurrentPosition = ((EndPosition-StartPosition) * FractionOfMove) + StartPosition. This would give you the same result as RCServo, and same as stock MRC-3024 moves, and many others.
Doing what I often do, I'm going to put in a few notes: I played with the math Sine function for acceleration / deceleration, but found it to be problematic when interrupting a move. It was also more CPU-intensive. I also went after the true calculations for the physics of trajectories and acceleration, which although was an interesting dive into calculus (not my strongest area), also resulted in CPU-intensive code. The Calculus-oriented physics calculations resulted in wide-swinging waves, often overshooting position and reversing when things (duration or end position) changed during the move.
I thought about it, a lot. I wanted simple calculations without all the CPU use and fuss. I started just looking at Acceleration. I ended up with Acceleration as FractionOfMove * Distance * FractionOfMove (note, "linear" movement is simply FractionOfMove * Distance). This gives a nice acceleration curve. At the start of the move, distance will be zero (0 * Distance * 0). At the end of the move, the result is the distance (1 * Distance * 1). The result inbetween is a nice acceleration curve. I used this same principle for decel, accelerating to .5 of the move as the end point, and reversing the values for decel as in the code. This worked nicely.
Now, on to interrupting a move:
Because by nature a timer is threaded, it can also be interrupted, and things can change that affect what happens in the timer external of the timer's code. This is bad for movement, but we've already dealt with that. It is GOOD for being able to alter what the timer does.
We still have a problem, if anyone's paying attention.
We can't directly change the end time or the end position without grossly affecting motion. The servo will suddenly feel the need to be somewhere else if end time or end position is changed during the move, and that's bad.
What to do, what to do? We change our destination GRADUALLY. This is speaking of code I haven't yet written (mainly due to focusing on the sound card problem as per my other thread here), so I'm going to throw out some ideas in my head that I haven't coded yet:
* Calculate the difference in the new end position / end time versus current, and use my 0,1,2 theory to apply the change. This theory basically means that for a change's net value to be a factor of 1 (itself), it must transition from factors 0 to 2 fractionally over time so as not to be abrupt. In code, this would mean from the time the change is made to a new end time, the end time would be altered very gradually, then more and more as the end time nears. This could mean ending sooner or later. Imagine drawing out a deceleration, or shortening up a deceleration. Your position at time of change would be offset by 0 * change, at the midpoint from start of change to end of move by 1 * change, and at the end, offset by 2 * change. Same is true for a position being changed. The problem I see here is the possibility for abrupt changes towards the end of the move if the intended change is drastic- but then, if a change is drastic, wouldn't the resulting movement be equally drastic?
* Do something creative with the last move's delta (position over time) and the already written accel / decel code. Not sure how much trouble this will cause, I will end up experimenting a bit with this one.
So there we go, my $.02 on motion and servos.
*** General Update ***
I have been working on board design for the sound card for Roboard, and am getting close to something that should work. With the costs involved, I'm very tempted NOT to implement too much on this board. Right now, I'm thinking of adding a microphone preamp, a 1 to 2 watt amplifier, and maybe a "steering" circuit to accept 2 microphone inputs and generate an analog signal relating to left-to-right panning of signal source (this would support "turning of head to listen", and would take load off CPU to do the same in code). As much as I'd like to integrate a power distribution system consisting of DC-to-DC converters with digitally tunable power output, the cost would be ridiculous in low volume, likely approaching the cost of Roboard itself (and the board would be a bit larger, and heavier, etc). That part of my project will probably have to be separate, which may work better in distributing the components around the 'bot a bit. PCB's will be a bit steep on price, and I need to buy a hot-air reflow station. Further, the board's got to be right from the start. Anyone betting on a disaster here? I almost am.
I've also repaired a few "broken tooth" HSR-5498SG servos. One little "karbonite" gear in the middle of the steel ones, and no surprise, IT is the one that fails.
Regarding the e-chain based hands, I have a couple micro servos sitting on the bench to repair, the pot leads are stressed, and they only work when magic finger pressure is applied just so. I have fine-gauge silicone wire I'm using to isolate the board from the stressed pot leads- tedious work on the micro servos. As for the hands, I have some drawings for some areas I know will not change, and I have what may be a final concept for hand bracketry sketched on paper. I need to fab some parts in polycarb, test the fit, and finalize the bracket dimensions. Then, I can CNC some brackets and build the hands.
I KNOW the hands will work out fine, the only problem I have left is to add wrist rotation in what is almost zero space. I'm not sure what I'm going to do there. I'm just going to build the hands as tight as I can and work from there. Worst case scenario, I hang a big servo out of the way and rotate the hand at a bearing joint.
Hips. I might get below the 4mm height I was thinking they were going to add. I'm going to experiment with some "Nylatron" for the thrust part of the bearing. If it doesn't stick, that's what I'm going to use. If it DOES stick, I'm going to try nylatron as a ball bearing surface, order some bearing balls, and make a ball cage out of UHMW or teflon for the thrust bearing. The problem I'm thinking I'm going to have is that the 2 x 1mm thick thrust washers and 2mm thick roller bearing assembly are just too heavy. It's a lot of weight, and they're very over-rated for this use (something like >1000 pounds thrust). They slap right together with the center bearing, but I want something lighter weight. Besides, if I cut the thrust washers myself, I can integrate a pulley on the circumference for rotating the joint (otherwise, I'd have to add it separately).
That's all for now...
Take Care,
Paul