PID control help.
Hello,
I am working with the IR sensors and the boebot. I did the exercises in the book, and then i got interested in combatting the jitter as much as possible.
I tried setting up the sensors as suggested here: http://forums.parallax.com/showthread.php?p=561496
That helped, but the bot was still jittery.
Then I tried averaging out the sensor input(code in post referenced above). That slowed the jitter considerably, but I still wanted more.
Andy suggested that I try using PID control as per his excellent write-up: http://forums.parallax.com/showthread.php?p=529609
So now I am working on that, but I am having trouble getting it to work.
I think that there is a problem with my attempts at limiting the signal range that gets sent to the servos with the MIN 650 MAX 850 commands
What happens is that the program gives the expected output for a bit, and then sends the opposite of what would be expected. For example the left servo will get 650 for a bit, and then it will suddenly switch to 850 even though it should continue at 650 based on sensor input.
Here is the code:
I have also attached a csv file (.txt format) that has data generated by the program.
I am working with the IR sensors and the boebot. I did the exercises in the book, and then i got interested in combatting the jitter as much as possible.
I tried setting up the sensors as suggested here: http://forums.parallax.com/showthread.php?p=561496
That helped, but the bot was still jittery.
Then I tried averaging out the sensor input(code in post referenced above). That slowed the jitter considerably, but I still wanted more.
Andy suggested that I try using PID control as per his excellent write-up: http://forums.parallax.com/showthread.php?p=529609
So now I am working on that, but I am having trouble getting it to work.
I think that there is a problem with my attempts at limiting the signal range that gets sent to the servos with the MIN 650 MAX 850 commands
What happens is that the program gives the expected output for a bit, and then sends the opposite of what would be expected. For example the left servo will get 650 for a bit, and then it will suddenly switch to 850 even though it should continue at 650 based on sensor input.
Here is the code:
' -----[noparse][[/noparse] Title ]-------------------------------------------------------------- ' FollowingBoeBotWithNewDistanceCircuit.bs2 ' Boe-Bot adjusts its position to keep objects it detects in zone 4. ' {$STAMP BS2} ' Stamp directive. ' {$PBASIC 2.5} ' PBASIC directive. ' -----[noparse][[/noparse] Constants ]---------------------------------------------------------- SetPoint CON 4 CenterPulse CON 750 Kp CON 20 ' Proportionality constant Ki CON 20 ' Integral constant Kd CON 20 ' Derivative constant Current CON 0 ' Array index - current error Accumulator CON 1 ' Array index - accumulated error Previous CON 2 ' Array index - previous error Delta CON 3 ' Array index - change in error ' -----[noparse][[/noparse] Variables ]---------------------------------------------------------- errorLeft VAR Word(4) ' Four different types of errors errorRight VAR Word(4) irDetectLeft VAR Bit irDetectRight VAR Bit distanceLeft VAR Nib distanceRight VAR Nib pulseLeft VAR Word pulseRight VAR Word ' -----[noparse][[/noparse] Initialization ]----------------------------------------------------- FREQOUT 1, 500, 3000 DEBUG "er,dist,p,i,d,Pulse,er(acc),er,dist,p,i,d,Pulse,er(acc)", CR ' -----[noparse][[/noparse] Main Routine ]------------------------------------------------------- DO GOSUB Get_Ir_Distances GOSUB Calculate_Drive GOSUB Send_Pulse LOOP ' -----[noparse][[/noparse] Subroutine - Get IR Distances ]-------------------------------------- Get_Ir_Distances: distanceLeft = 0 distanceRight = 0 FOR DIRB = 1 TO 7 FREQOUT 7,1,38500 'left ir led irDetectLeft = IN8 distanceLeft = distanceLeft + irDetectLeft FREQOUT 3,1,38500 'right ir led irDetectRight = IN2 distanceRight = distanceRight + irDetectRight NEXT RETURN ' -----[noparse][[/noparse] Subroutine - Calculate Drive ]---------------------------------------- Calculate_Drive: 'Calculate left error / proportional errorLeft(Current) = SetPoint - distanceLeft 'integral errorLeft(Accumulator) = errorLeft(Accumulator) + errorLeft(Current) 'derivative errorLeft(Delta) = errorLeft(Current) - errorLeft(Previous) ' set previous errorLeft(Previous) = errorLeft(Current) ' Calculate output. pulseLeft = CenterPulse - ((Kp * errorLeft(current)) + (Ki * errorLeft(Accumulator)) + (Kd * errorLeft(delta))) MIN 650 MAX 850 DEBUG SDEC errorLeft(Current), ",", DEC1 distanceLeft, ",", SDEC Kp * errorLeft(current), "," DEBUG SDEC Ki * errorLeft(Accumulator), ",", SDEC Kd * errorLeft(delta), ",", SDEC pulseLeft, "," DEBUG SDEC errorLeft(Accumulator), "," ' Calculate right error / proportional errorRight(Current) = SetPoint - distanceRight 'integral errorRight(Accumulator) = errorRight(Accumulator) + errorRight(Current) 'derivative errorRight(Delta) = errorRight(Current) - errorRight(Previous) 'set previous errorRight(Previous) = errorRight(Current) 'Calculate output. pulseRight = CenterPulse + ((Kp * errorRight(current)) + (Ki * errorRight(Accumulator)) + (Kd * errorRight(delta))) MIN 650 MAX 850 DEBUG SDEC errorRight(Current), ",", DEC1 distanceRight, ",", SDEC Kp * errorRight(current), "," DEBUG SDEC Ki * errorRight(Accumulator), ",", SDEC Kd * errorRight(delta), ",", SDEC pulseRight, "," DEBUG SDEC errorRight(Accumulator), CR, CRSRX, 0 RETURN ' -----[noparse][[/noparse] Subroutine - Send Pulse ]--------------------------------------------- Send_Pulse: PULSOUT 13,pulseLeft PULSOUT 12,pulseRight PAUSE 5 RETURN
I have also attached a csv file (.txt format) that has data generated by the program.
Comments
For example, if I add a final calculated output of -1300 to 650, I get -650 --- but this won't MIN to 650 it will MAX to 850 because MIN and MAX work on variables like they were odometers -- if you roll them backwards they start counting from the highest number down again. If you MIN/MAX them at that point, you'll get the MAX because the number will effectively be very high.
Basically, MIN MAX will not work properly on negative values, so you need to account for that possibility either through logic or math.
There are tricks around this, the most common but not the fastest code wise is:
There are other ways too; see Tracy Allen's site for more discussion. In your case, you would need to check sign of your output before adding to the centerpulse and limiting. You might also need to limit the sheer output of the I portion of your controller ("integral windup" = the accumulated error goes to infinity):
I would also try dialing your I K constant down to 0, your I D constant down to 0 and set just your P K constant so the bot behaves without huge oscillations. Then start dialing up the I K and D K until you get smooth response without jitter. You may also need to allow for K values of less than 1 (i.e., what if the ideal PK is .75?)
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
When the going gets weird, the weird turn pro. -- HST
Post Edited (Zoot) : 11/21/2006 4:52:29 PM GMT
thanks.
I had looked up MAX and MIN in the PBASIC reference, but it didn't help me figure out what was wrong. I still don't completely understand the math limitations, of the BS2, but I am getting better at it.
I will take a look at this this evening when I get a chance to sit down with the bot again. I forgot to mention in the original post that the constants were way too big. I left them that way since it gave me a quick way to see what was happening in the debug terminal. I will see if I can implement your solution. I hope I have enough RAM left to do it. As it was I think I only have space for one or two more WORD variables.
Also could you give me a link to Tracy Allen's site?
Another way to put MAX MIN limitation --- this won't work:
X = -1300
X = X MIN 1000 MAX 1200
(now X = 1200)
Why? because MIN and MAX don't see the negative sign, and on a BS2, the "negative" is really just BIT15, which means if it goes to 1 (for a "negative" number) it is SEEN as very HIGH number.
So you need to get rid of the negative sign, limit it, then restore negative if it's applicable. ABS just takes the - sign off ( ABS(-1000) = 1000 and ABS(1000) = 1000 ).
X = -1300
IF X.BIT15 = 1 THEN 'bit15 = 1 means a negative, which is less than 0, so use the MIN!
X = 1000
ELSE
X = X MIN 1000 MAX 1200 'if it's positive, then limit normally
ENDIF
(now X = 1000)
Tracy Allen -- BS2 math guru -- http://emesystems.com -- http://emesystems.com/BS2index.htm#math
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
When the going gets weird, the weird turn pro. -- HST
thanks. I finally got back to this, and using BIT15 works nicely.
here is how I am doing the calculation now:
temp is a Word
Also thanks for the Tracy Allen link. Great stuff there!
If you ever need fractional constants for your KP, KI and KD you can do it like this. Use K's that are in 100ths -- e.g. 100 would be 1, 150 would be 1.5, etc. Then multiply by 100 first, dividing back by 100 at the end:
To be really precise, though add 50 before dividing (this will round .5 up and < .5 down):
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
When the going gets weird, the weird turn pro. -- HST
I wrote a PID engine so that setpoints, offsets, constants, etc. are stored in tables in EE and then a single set of subroutines runs 'em through the mill and delivers final output. If you are only doing one set of PID moves, then just writing it out is good, but if you have to run several motors, sensors, whatever, each through their own set of PID constants, it can be nice to have a PID calculator that does all the grunt work.
http://forums.parallax.com/attachment.php?attachmentid=42031
There's a lot of I/O and variable declarations in the code that you can skip -- the pertinent part is the EE lookup tables for PID constants, offsets, direction, etc. and the routines that call the actual PID sub -- scroll down the code and see IRbPID, for example -- an EE addr for the data is set, an SPRAM address is set for retrieving the sensor data, then it runs through the mill. You could easily change the routines to use regular variables instead of retrieved scratch pad variables. In any case, GOSUB PID and it's associated routines are what do the bulk of the work; these should be clear enough. This is actually older code, my current engine is more compact, but is harder to read and doesn't have many comments.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
When the going gets weird, the weird turn pro. -- HST