|
|
View previous topic :: View next topic |
Author |
Message |
vinniewryan
Joined: 29 Jul 2009 Posts: 154 Location: at work
|
High speed motor control (propellers) |
Posted: Tue Sep 14, 2010 1:58 pm |
|
|
I recently built a flying unit that has 4 motors with propellers, an accelerometer, and is controlled by a 16f684 mcu. The function of this unit is to simply keep the unit in the air, and using the accelerometer, balance out so it's always flying level. The problem I've come across is that I don't think the MCU is fast enough (4Mhz) to do this kind of control.
My program is simple. When I turn the unit on, I hold it level and press a button, and the MCU reads the X and Y axes of the accelerometer and saves them as reference variables XC and YC. The C is for 'Center' because this is the value that the ADC reads when the unit is level. Then it enters normal operation mode, turns on all 4 motors, reads X and Y values of accelerometer and compares them to the reference values XC and YC. If X > or < XC, the program tells the appropriate motors to slow down or speed up so the unit can correct its tilt. So in a nutshell, all the program is doing is keeping the flyer aloft and level so it can stay in one place in the air.
In reality, when I turn the flyer on, it tries to correct its tilt but it's impossible to get it to correct accurately enough for it to stay level. I've spent hours, day, weeks adding thresholds, offsets, sensitivities in every possible way to get it to not over or under correct, but no matter how much I improve its performence, I can't get it to work well enough.
My current program is constantly pulsing output_high to each motor, and if that motor needs to decrease speed, I'll output_low for a calculated time(in us) then output_high again. This is obviously not the right way to control motor speed, so I'm wondering what a better, proven method would be.
Would it be wise to use a PWM to control motor speed for 4 separate motors, and if so, is it possible to change the pwm speed fast enough to control all 4 motors simultaneously? Is there a better approach for controlling 4 propellors? Is a 40MHZ oscillator speed fast enough to perform such precise algorithms ? Any suggestions or ideas are appreciated.
Thoughts? _________________ Vinnie Ryan |
|
|
Mike Walne
Joined: 19 Feb 2004 Posts: 1785 Location: Boston Spa UK
|
|
Posted: Tue Sep 14, 2010 5:17 pm |
|
|
Sounds like you've got a classic PID loop control problem.
If you disable one axis, and use only two motors, can you achieve control by hand using a potentiometer (or something similar)?
(i.e. use clockwise rotation of the pot to increase the speed of one motor and decrease the speed of the other & vice-versa)
Mike Walne |
|
|
SherpaDoug
Joined: 07 Sep 2003 Posts: 1640 Location: Cape Cod Mass USA
|
|
Posted: Tue Sep 14, 2010 5:49 pm |
|
|
What makes you think processor speed is a problem? Are you using a paced loop to regulate the control loop speed? If so you should be able to tell if there is extra time left at the end of the old cycle before the next cycle is scheduled to begin.
If your code is at all efficient I don't expect processing speed should be a problem. I hope you are not using floating point math! _________________ The search for better is endless. Instead simply find very good and get the job done. |
|
|
vinniewryan
Joined: 29 Jul 2009 Posts: 154 Location: at work
|
|
Posted: Wed Sep 15, 2010 12:57 am |
|
|
Now that I think about it from another perspective, I'm certain it's not processing speed, I'm just stuck and don't know what to try next, it feels like I've tried every method.
I would post the code but it's pretty long, has a lot of variables, and would be a headache for anyone to try and examine.
In basic form, there are 2 variables for each motor. We'll call them LMONT and LMOFFT, these stand for 'Left Motor On time' and 'Left Motor Off time', and they represent the amount of time the motor is pulsed on and off respectively. Also, there are two more variables, 'LeftMotorOnCounter' and LeftMotorOffCounter', and these are used to keep track of how long the motor has been on and off for.
When I turn the unit on, the first thing it does is wait for me to hold it level and press the button to calibrate it. When I press the button, it saves the ADC value from the X axis as XC, and the value from the Y axis as YC, as I explained before. At this point, it knows what value each axis should be reading for it to be level and has saved them as XC and YC.
Next, the code enters normal operation. It detects whether it's level or not, and if not, it calculates a new value for LMOFFT and LMONT.
Code: |
//initially all variables = 0.
If (X>XC) // the unit is tilting left
{
LMONT+=(X-XC); //increase the amount of time the left motor stays on for based on how far from level (XC) it's tilting
LMOFFT-=(X-XC); //decrease the amount of time the left motor stays off for based on how far from level (XC) it's tilting
}
//increasing LMONT and decreasing LMOFFT makes the left motor pulse on longer and pulse off for a shorter period, resulting in the left motor having higher speed/ thrust
//now we know the unit is tilting left and have calculated how long to turn the left motor on and off for
if(LeftMotorOnCounter<LMONT)
{
output_high("Left Motor")
LeftMotorOnCounter++; //increase until the motor has remained on for the amount of time calculated (LMONT)
}
else if(LeftMotorOnCounter>=LMONT) //counter is equal to LMONT, motor has been on for amount of time calculated
{
if(LeftMotorOffCounter<LMOFFT)
{
output_low(Left Motor);
LeftMotorOffCounter++; //increase until the motor has remained off for the amount of time calculated (LMOFFT)
}
else
{
//reset variables so this code can run again
set LeftMotorOnCounter to 0;
set LeftMotorOffCounter to 0;
}
}
|
The above code will adjust the speed of the left motor based on how far the unit is tilted left, and will loop over and over. This code can be copied/pasted and slightly edited to account for the right motor, and changing the X variables to Y, can account for the Front and Back motor. Once all motors and both axes are accounted for, the unit should be able to correct a left tilt, right tilt, forewards tilt, and backwards tilt, and adjust the speed of each motor to very accurately have variable speed adjustment based on how far it's tilted in any direction.
The worst part is, this method works, the motors adjust speed, it tries to correct its flight, I can tell when I hold it from a corner, but when I let go and let it free fly, it begins to rotate and constantly over-correct resulting in fairly sudden loss of control (crash). The motors are mounted in a proven configuration, one in the front spinning CW, one in the back spinning CW, one on the left spinning CCW, and one on the right spinning CCW. I can turn all of the motors on and it will fly straight up without rotating.
So as you see, the code is set up to have absolutely no delay between each cycle. The actual code has many other variables being calculated (Threshold for each axis, multipliers for output times, etc) so I won't post it as of yet, but what I have posted is the basic foundation of how the code calculates how long to turn the motors on and off for, so keep in mind that the above code runs all the way through thousands of times per second, which means the motors pulse on and off nearly 20 times per second, which is why I was thinking a faster MCU would make things respond more quickly, but now I realize that I was incorrect, because in order for a motor to reach a speed fast enough to thrust the propellor with enough power to lift, that motor has to be pulsed ON for a certain minimum amount of time, so a faster oscillator speed really won't help at all.
I know this is hard to diagnose without seeing the thing fly (or try to), but my goal here is to seek if my method is efficient for this type of operation.
Thoughts? Questions? Suggestion? _________________ Vinnie Ryan |
|
|
Wayne_
Joined: 10 Oct 2007 Posts: 681
|
|
Posted: Wed Sep 15, 2010 2:22 am |
|
|
My firstthoughts are with this:-
Code: |
LMONT+=(X-XC); //increase the amount of time the left motor stays on for based on how far from level (XC) it's tilting
LMOFFT-=(X-XC); //decrease the amount of time the left motor stays off for based on how far from level (XC) it's tilting
|
You are increasing LMONT but never decreasing, the opposite goes for LMOFF!
Rather than complicating the issue by having 2 adjustments you should just have 1 for a PWM output and increase or decrease it to make the change:-
Code: |
if (X < XC)
lmont+= (X - XC);
else if (X > XC)
lmont-= (X - XC);
|
This needs looking at as well, How quickly does the accelerometer respond to the changes. If left is low and you speed the motor up you don't want to keep increasing the speed if it is correcting (going up) or you will end up with a big overshoot. As it gets closer to center you may want to reduce the speed to minimise overshoot.
And have a fixed LMOFF time which then becomes your pulse width.
Code: |
#define LMOFF 100;
if (leftMotorOnCounter < lmont)
output_low(LEFTMOTOR);
else if (leftMotorOnCounter < LMOFF)
output_low(LEFTMOTOR);
else
leftMotorOnCounter = 0; // Start again
|
And just a quick note about coding, All caps is usually for constants (defines).
Underscores can be used to seperate the name e.g. LEFT_MOTOR.
Vars are usually all lowercase or a mixture of lower+caps starting with a lower case. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19520
|
|
Posted: Wed Sep 15, 2010 2:33 am |
|
|
As Mike says, fairly classic.
Problem is the lags involved.
Imagine for a second, that the motor responded 'instantly' to a change in power. Even then, it'd take time for the increase/decrease in thrust to actually correct the platform. Worse, the motors take time to respond, and it takes time for a change to even affect the motor control.
So, the platform tilts right. Control system says 'tilting right', and increases the power to the right motor. Next time round, it still has not responded, so it increases it further. This perhaps happens for several cycles. The platform then starts to respond, beginning to level, but is still tilting right, so another couple of cycles occur before finally the system sees the platform at 'level', but by then, perhaps 20% more power has been applied to the right motor than was actually needed to level it, so it now overshoots...
The classic solution, is an algorithm called 'PID'. What you do, is start with a 'mid range' motor power value. Effectively the average of the power that has been applied long term to the motor. If the platform tends to spend more time tilting right than left, you fractionally increase the power to the right motor. This is the 'long term' tweak value, the 'I' term (integral of past responses). You then look at the immediate error. Tilting right, so you increase the right motor power (the 'P' term), giving a response 'proportional' to the error. You then though look at the current change. Is the platform's angle getting worse, or has it started to slow, or is it beginning to tilt back. If the last two, you _reduce_ the correction applied, or in the last case, actually start to reverse it. Effectively you respond to the 'rate of change' of the position (the 'differential' term - D - how much the position has changed since 'last time').
What you have at the moment, is a rather simple pure 'P' term response, so with the lags both in motor responses, inertia, and even the sensor - the value you read always lags the real position, you end up perpetually overshooting. A 'genuine' P term, would make the adjustment applied actually reflect how far off level the system is, reducing the tweaks at small angles, which would help straight away...
Unfortunately, a full PID algorithm, will use a lot more processing power, so the processor speed will probably begin to be an issue. However the internal oscillator on the PIC you have supports operation at 8MHz, so you can increase the speed by a simple program change, which will help. You might want to try something a little simpler, but based upon this approach. Imaging that you store the required power in a two byte value, with the MSB, being the 'power to apply'. Then in your loop, look at how far off angle the system is, with just a series of step tests. If the unit is badly tilted, add/subtract 256 to the 16bit value, but if it is titled just a few degrees, 128, while if it is tilted, but hardly at all, just 1. Then use the MSB as the power to the motor. This way the adjustment will be very small and slow, if the platform is only tilted a little. Then also look at the change from last time. If the tilt is _less_ than last time, just decrease the adjustment by a single unit.
This gives a very crude PD correction. You can tune the 'factors', by adjusting the size of the numbers added.
Best Wishes |
|
|
vinniewryan
Joined: 29 Jul 2009 Posts: 154 Location: at work
|
|
Posted: Wed Sep 15, 2010 2:50 am |
|
|
Before I reply to the rest of your comments, let me address a mistake that you both brought up.
This is what I originally posted:
Code: |
If (X>XC) // the unit is tilting left
{
LMONT+=(X-XC); //increase the amount of time the left motor stays on for based on how far from level (XC) it's tilting
LMOFFT-=(X-XC); //decrease the amount of time the left motor stays off for based on how far from level (XC) it's tilting
}
|
You're right, LMONT will constantly increase (and LMOFFT will constantly decrease) until the motor has corrected the tilt so much that it will then over correct. This was a mistake on my part in the sample code I typed up, the actual program was like this:
Code: |
int16 VAR=XXX; // var is the amount of time the motor will pulse on or off assuming the unit is perfectly level
If (X>XC) // the unit is tilting left
{
LMONT=VAR+(X-XC); //increase the amount of time the left motor stays on for based on how far from level (XC) it's tilting
LMOFFT=VAR-(X-XC); //decrease the amount of time the left motor stays off for based on how far from level (XC) it's tilting
}
|
So it always sets LMONT and LMOFFT to VAR first, then adds or subtracts the amount of time needed to make the appropriate correction.
This way, if the unit is tilting slightly left, the left motor will increase slightly, and as it tilts more and more, its power will increase, so the output pulse time reflects/ mimmicks the amount it's tilted. _________________ Vinnie Ryan |
|
|
vinniewryan
Joined: 29 Jul 2009 Posts: 154 Location: at work
|
|
Posted: Wed Sep 15, 2010 3:15 am |
|
|
Now that that's out of the way,
Thanks for the responses and suggestions. You suggested setting lmofft to a fixed value as it's my pulse width, I'll try that first. It should help smoothen out the output to the motor. Also, thanks for the syntax pointer, I didn't realize there was actually a reason I never see all CAPS in variable declarations.
Ttelmah,
Thank you for the indepth description, you certainly gave me some ideas and things to try. One idea is, instead of the power of the left motor mirroring the amount of tilt present, to have a new variable that allows me to increment the pulse time to the left motor, kind of like a buffer.
Another thing I have in the actual program that's not here, is a threshold (th) for each axis, which I'll add in just so you have an idea of how I'm preventing constant correction.
Code: |
int16 lmonb=0; //Left Motor On Buffer
int8 th=5; //threshold
If (x>xc+th) // the unit is tilting left
lmonb=var+(x-xc); //set the buffer to the value that lmont should be
if(lmonb>lmont)
lmont++; //increase lmont until it = lmonb with a slight delay between each increase
else
lmont=lmonb;
...
...
...
else If (x<xc-th) // the unit is tilting right
|
This way if the unit tilts left, the program will calculate where lmont needs to end up, but can gradually bring it there, but then if it starts tilting back towards center (else) it instantly sets lmont where it should be. Simply put, it slowly increases motor speed, but quickly decreases it. Also, the threshold (th) adds a 'true' center so it won't always be correcting even while it's perfectly level, although if the threshold is too high it can cause problems too. I've found a very small threshold to help a lot. _________________ Vinnie Ryan |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9229 Location: Greensville,Ontario
|
|
Posted: Wed Sep 15, 2010 5:23 am |
|
|
I used to build university level helis like this years ago and know that simple PID will control them(16F877,20MHz) so it is doable!
Don't know if you thought or do this but ...
Please consider that if it's tilting down left to not only incerase the Left motor but ALSO decrease the RIGHT motor at the same time. This will increase the response time.
Also be sure to add some filter caps to the motors.Thay generate noise that your system might pickup ,giving false readings..
Also if you use 8bit math it's faster and easier on the PIC , that's all I used.
Also prop pitch and diameter are critical. Changing to the next size up or down will definitly alter response/performance. And there will be the 'right' one for your heli.
Good luck
Jay |
|
|
SherpaDoug
Joined: 07 Sep 2003 Posts: 1640 Location: Cape Cod Mass USA
|
|
Posted: Wed Sep 15, 2010 5:38 am |
|
|
I think you are still missing an important piece of your control loop. You need to regulate the speed the whole loop operates at. Usually this is done with a "paced loop". The control loop is started by a timer, usually a flag set by a timer interrupt. Once the loop runs and all the measurements are done and motors adjusted, the loop waits for the timer before it starts over again.
This is crucial for a PID algorithm as the I and D terms are time dependent. If the loop speed changes due to the math execution time or the motor duty cycles you will never get things stable. _________________ The search for better is endless. Instead simply find very good and get the job done. |
|
|
newguy
Joined: 24 Jun 2004 Posts: 1908
|
|
Posted: Wed Sep 15, 2010 7:28 am |
|
|
Google "PID Without a PhD" by Tim Wescott. It's filled with invaluable background information. |
|
|
vinniewryan
Joined: 29 Jul 2009 Posts: 154 Location: at work
|
|
Posted: Wed Sep 15, 2010 6:59 pm |
|
|
temtronic, I'm glad you have experience with this type of project, and you've already made many valuable suggestions. I'll certainly make the opposing motor decrease as the other increases. I do have filter caps on the motors, along with diodes on the power lines to decrease system noise, and doing so made a huge improvement. Also I'll change my variables to 8 bit, I was under the impression that using 16 bit would give me a wider range of values off the ADC axes. The propellors have been specially designed specifically for this project's size/body by an experienced engineer, so now it's up to my programming to make things work out. Thanks for the info, I'll let you know how it works out!
SherpaDoug, I understand how a paced loop would help normalize the timing structure, but it brings up one big question; When "The control loop is started by a timer, usually a flag set by a timer interrupt. Once the loop runs and all the measurements are done and motors adjusted, the loop waits for the timer before it starts over again.", what state should I leave the motors in while they're 'waiting' for the timer before the loop starts over? Should they be on or off? Also, are you suggesting I create a simple timer, or use the built in hardware timer to keep track of the loop timing?
newguy, wow I just looked up the PDF of "PID Without a PhD", read through a few sections and it looks like it will be very helpful, I'll read it tonight, it seems I can learn a lot about what SherpaDoug is suggesting in that document.
Thanks everyone, I have a LOT of things to change and try, and every suggestion brings me closer to succeeding in getting this thing to fly level! _________________ Vinnie Ryan |
|
|
newguy
Joined: 24 Jun 2004 Posts: 1908
|
|
Posted: Wed Sep 15, 2010 7:22 pm |
|
|
Regarding the paced loop, you'll definitely want to do that with a hardware timer and its associated interrupt. The general idea is to run the loop at least 10x faster than any physical 'catastrophic' change will take to occur.
With what you're building, I'd try to measure the time it takes for the thing to tilt some critical amount - 45 degrees? 30 degrees? 5 degrees? (whatever you deem to be critical) - if one motor is running full out while the others are idling/mid-range/whatever. If that takes 500ms, then your control loop needs to run every 50ms, if not faster.
Your timer ISR, once it fires, should measure the x-y-z tilt, then set a flag for your main loop that alerts it that a new tilt is available, and exit. The main loop is where all your processing occurs (motor drive levels), and is triggered by the flag that new data is available. Once the motor drive levels are set by this routine, they stay there until the next time that new data is available. In this manner, your program spends most of its time 'twiddling its thumbs.' ....And that is honestly the best way to lay out a program - do nothing until you're prompted to react to some outside event. Plus you're guaranteed to have rock solid timing for your PID algorithm(s).
The only hard part will be to tune the PID algorithms, but even that shouldn't be that hard. I'd try to isolate the front/back and left/right tuning by disabling a pair of motors and mounting the thing on a knife's edge so that it can only tilt L/R when tuning the L/R algorithm and F/B when tuning the F/B algorithm. Once you get each pair optimized (and depending on the balance/geometry they may end up being identical), you're pretty much guaranteed that they'll work together to get the thing to hover perfectly right off the bat. |
|
|
SherpaDoug
Joined: 07 Sep 2003 Posts: 1640 Location: Cape Cod Mass USA
|
|
Posted: Thu Sep 16, 2010 8:52 am |
|
|
I think Newguy answered your questions pretty well. Keep an eye on the "thumb twiddling" time. When that time gets close to zero you may need to go to a faster CPU speed. You probably don't need a whole lot of resolution so 8 bit ints should do.
You might have a fast timer interrupt to throttle your motors. Each motor has a thrust setting number. The fast interrupt counts down a time number which rolls over at 4 or 8 bits, and compares to each motor's thrust number. If the thrust number is bigger the motor is switched ON, else the motor is switched OFF. Your main program updates the thrust numbers and lets the fast interrupt apply the numbers to the motors on its own.
If you mount the craft to tilt on a horizontal string or wire you should be able to work on one axis at a time. _________________ The search for better is endless. Instead simply find very good and get the job done. |
|
|
vinniewryan
Joined: 29 Jul 2009 Posts: 154 Location: at work
|
|
Posted: Mon Oct 25, 2010 6:43 pm |
|
|
Thanks for the advice, I took your suggestion SherpaDoug, mounted the flyer on a tensioned fishing line and tested each axis. The first problem I found was that the weight was too high (top heavy), so the downward acceleration was much more powerful than the correcting upward-thrust acceleration. After mounting the weight on the underside of the unit, I immediately saw a major improvement. I was able to make it balance flawlessly on each axis.
Next, I detached the unit from the string so it could free-fly. The first problem that came up was rotation. There is no onboard gyro so I have no way of detecting rotation, so I made a few programming changes, and with the help of all of your advice I was able to create a PID algorithm that pulses the motors at 100hz and it worked like a charm. Now the thing flies level, and no rotation. The next step is to mount my custom $0.30 cent gyro for yaw detection.
Thanks for all the help! I couldn't have done it without your suggestions! _________________ Vinnie Ryan |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|