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 = CenterPulse - ((Kp * errorLeft(current)) + (Ki * errorLeft(Accumulator)) + (Kd * errorLeft(delta))) IF temp.BIT15 = 1 THEN 'bit15 = 1 means a negative, which is less than 0, so use the MIN! pulseLeft = 650 ELSE pulseLeft = temp MIN 650 MAX 850 'if it's positive, then limit normally ENDIFtemp 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