PID boost control code
PID boost control code
To avoid polluting Tephra's V5 thread. To discuss V6/PID boost control. Code below is from my 2002 EBC project using Atmel AVR microcontroller and BASCOM-AVR. It was for a slowbaru hence only 20PSI at the time... Discuss...
The throttle position and MAP sensor are read from ADC channels. So are the gains for P, I and D.
The throttle position and MAP sensor are read from ADC channels. So are the gains for P, I and D.
Code:
Dim Setpoint As Byte ' Setpoint
Dim Tps As Byte 'throttle position
Dim Map As Byte ' MAP
Dim Cv As Integer ' output
Dim Pterm As Integer
Dim Dterm As Integer
Dim Iterm As Integer
Dim Kp As Integer ' gain
Dim Kd As Integer
Dim Ki As Integer
Dim Duty As Byte ' target duty cycle if no error
Dim X As Word 'temporary unsigned variable for ADC
Dim Error As Integer ' Difference between Setpoint and Map
Dim Lastmap As Byte
Dim Integrate As Byte
Dim Sum_error As Integer
'still space for 8 more integers at least
Const Psi0 = 116 'ADC input for 0 PSI
Const Psi1 = 121
Const Psi2 = 126
Const Psi3 = 131
Const Psi17 = 200
Const Psi18 = 205
Const Psi19 = 210
Const Psi20 = 215 'ADC input for 20 PSI
Const Mindutycycle = 0
Const Maxdutycycle = 238 'dutycycle %*2.55
Const Tpstarget = 170 'throttle position reference
Const Mintps = 100
Config Adc = Single , Prescaler = Auto 'start analog/digital converter
Start Adc 'hardware configure 10 bits 0-5V
Config Portb = Output
Config Timer1 = Pwm , Pwm = 8 , Compare A Pwm = Clear Down , Prescale = 1024
'start PWM output 15.3Hz
Enable Interrupts 'each PWM cycle run loop
Enable Timer1
On Timer1 Calculate Nosave 'no registers to save
Setpoint = Psi18
Do 'endless loop until interrupt
Loop
Calculate:
X = Getadc(0) '10 bits unsigned
Shift X , Right , 2 'convert to 8 bit
Tps = X 'throttle position
X = Getadc(1) '10 bits unsigned
Shift X , Right , 2 'convert to 8 bit
Duty = X 'target duty cycle if no error
X = Getadc(5)
Shift X , Right , 2
Ki = X 'integral gain
X = Getadc(3) 'MAP sensor
Shift X , Right , 2
Map = X
X = Getadc(2)
Shift X , Right , 2
Kp = X 'proportional gain
X = Getadc(4)
Shift X , Right , 2
Kd = X 'differential gain
Error = Setpoint - Map
Portb = 63 'flash 4 LEDS to show how near to target
If Error > 0 Then
Reset Portb.5
If Error < 3 Then Reset Portb.4
End If
If Error < 0 Then
Reset Portb.2
If Error > -3 Then Reset Portb.3
End If
Dterm = Map - Lastmap 'caluclate differential part
Lastmap = Map
Dterm = Dterm * Kd
Dterm = Dterm / 128
Pterm = Kp * Error 'calculate proportional part
Pterm = Pterm / 128 'kp is in range 0-255
'representing 0-2 therefore divide by 128
'quick and dirty FP math using integers
'not using shifts because signed variable!
If Integrate = 1 Then
Sum_error = Sum_error + Error 'calculate integral part with antiwindup
Else
Sum_error = 0
End If
Iterm = Ki * Sum_error
Iterm = Iterm / 128
Integrate = 1 'set to integrate next time unless cancelled by conditions
Cv = Pterm + Dterm 'add all three together (can only add 2 variables at once)
Cv = Cv + Iterm
If Tps < Tpstarget Then
Cv = 0 'use static target at partial throttle
Integrate = 0
End If
Cv = Cv + Duty 'error adjust target duty cycle
If Map < Psi1 Then Cv = Mindutycycle 'if no boost shut up/rest solenoid
If Tps < Mintps Then Cv = Mindutycycle
If Map > Psi20 Then Cv = Mindutycycle 'overboost cutout for momentary 20PSI
If Cv <= Mindutycycle Then
Cv = Mindutycycle
Integrate = 0
End If
If Cv >= Maxdutycycle Then
Cv = Maxdutycycle
Integrate = 0
End If
Pwm1a = Cv 'adjust duty cycle output
Return
Last edited by jcsbanks; May 16, 2008 at 05:03 AM.
Useful info on PID control http://newton.ex.ac.uk/teaching/CDHW/Feedback/
Includes Excel spreadsheet that plots graphs with different gains for PID for an over controller! You can see the oscillations, over/underdamping etc from incorrect gains. Useful to experiment in a simulator before on the car unless you've tuned PID systems before. Certainly helped me with this project.
Includes Excel spreadsheet that plots graphs with different gains for PID for an over controller! You can see the oscillations, over/underdamping etc from incorrect gains. Useful to experiment in a simulator before on the car unless you've tuned PID systems before. Certainly helped me with this project.
Post what you've got it you like...
Gains were unitless as I did not convert pressures or duty cycles into real values but left them as integers as the microcontroller I used had no floating point.
Issues were of setting the gains, or the range available to something sensible, required a lot of experimentation. Also issues of sorting out integral wind up and small bugs in code that produce odd results.
I did find that it most definitely needed a starting duty cycle to work with, it wouldn't just find it without. So I got the duty cycle to approx that required to run the boost level then started to add proportional gain. This made a nice controller. I then added a small amount of integral gain, and in the end I think I left out derivative/differential as it was difficult to setup and I was juggling too many variables. When I got into the Subaru ECU ROM I abandoned the project, but I had intended to make it RPM aware, this is easy in our case because we have the variable sitting right there in the ECU
Gains were unitless as I did not convert pressures or duty cycles into real values but left them as integers as the microcontroller I used had no floating point.
Issues were of setting the gains, or the range available to something sensible, required a lot of experimentation. Also issues of sorting out integral wind up and small bugs in code that produce odd results.
I did find that it most definitely needed a starting duty cycle to work with, it wouldn't just find it without. So I got the duty cycle to approx that required to run the boost level then started to add proportional gain. This made a nice controller. I then added a small amount of integral gain, and in the end I think I left out derivative/differential as it was difficult to setup and I was juggling too many variables. When I got into the Subaru ECU ROM I abandoned the project, but I had intended to make it RPM aware, this is easy in our case because we have the variable sitting right there in the ECU
next week ill debug what code I have so it "runs" sensibly... then I can post it.
basically its going to use the current BDEL table to resolve what WGDC it needs...
If ppl like i can make a 3d map tps vs rpm vs psi but thats all detail we can worry about later
basically its going to use the current BDEL table to resolve what WGDC it needs...
If ppl like i can make a 3d map tps vs rpm vs psi but thats all detail we can worry about later
I've tuned a lot of PID systems in the past. Actually the stock BCS code pseudo acts like a PID system if you look at it in a certain way. Anyways if you get code up and running I'll for sure give it a shot.
Trending Topics
Just looking through the code again, the reason I maybe wasn't impressed with the derivative control is that I think the sign is wrong. By using map-lastmap then if the boost is rising then a positive differential gain would add further to the duty cycle, which is the opposite to what we require. I also read that the usual method is to differentiate the error, so again we would use setpoint-present value. So as we increase boost towards the target, the error would be quickly reducing which would reduce the derivative term and dampen the overshoot. Once we actually overshot then the error would then become negative and the differential term would take the inertia out of it, and as dexmix pointed out in the other thread effectively smooth it out.
Derivative (as I've been taught) is how far away you are from your target. I've always been cautioned to use it sparingly or not at all if I can. The reason being is that it tends to make systems unstable. Too much derivative on a fast control system like boost and you risk overshooting a lot.
Integral (grows bigger with being off of target for a long time) may be difficult. It might have to be triggered by TPS or something because this value could grow very large if just driving around normally for an hour. Or maybe it should be triggered by a certian psi of boost and untriggered when dropping below this boost. Anyways integral tends to make systems more stable.
Integral (grows bigger with being off of target for a long time) may be difficult. It might have to be triggered by TPS or something because this value could grow very large if just driving around normally for an hour. Or maybe it should be triggered by a certian psi of boost and untriggered when dropping below this boost. Anyways integral tends to make systems more stable.
I thought D was the difference between the error this time and the error lasttime
if if your target is 20psi, and last time you got 17psi, and this time you got 19psi, then D would be 2psi ((20-17)-(20-19))..
if if your target is 20psi, and last time you got 17psi, and this time you got 19psi, then D would be 2psi ((20-17)-(20-19))..
... so in that case the derivative gain would be negative to damp the rapidly rising boost.
If Map < Psi1 Then Cv = Mindutycycle 'if no boost shut up/rest solenoid
If Tps < Mintps Then Cv = Mindutycycle
If Map > Psi20 Then Cv = Mindutycycle 'overboost cutout for momentary 20PSI
If Cv <= Mindutycycle Then
Cv = Mindutycycle
Integrate = 0
End If
If Cv >= Maxdutycycle Then
Cv = Maxdutycycle
Integrate = 0
End If
The above bits of code were trying to stop the problem with integral wind up you mention dan l. So it has a TPS trigger and boost level only above which it will allow integration. It also turns it off if it has wound up to either end of the duty cycle range - I think this may be undesirable?
If Map < Psi1 Then Cv = Mindutycycle 'if no boost shut up/rest solenoid
If Tps < Mintps Then Cv = Mindutycycle
If Map > Psi20 Then Cv = Mindutycycle 'overboost cutout for momentary 20PSI
If Cv <= Mindutycycle Then
Cv = Mindutycycle
Integrate = 0
End If
If Cv >= Maxdutycycle Then
Cv = Maxdutycycle
Integrate = 0
End If
The above bits of code were trying to stop the problem with integral wind up you mention dan l. So it has a TPS trigger and boost level only above which it will allow integration. It also turns it off if it has wound up to either end of the duty cycle range - I think this may be undesirable?
For the rest of us who haven't been introduced, the everpresent wiki helps bring the understanding level up a bit also:
http://en.wikipedia.org/wiki/PID_controller
http://en.wikipedia.org/wiki/PID_controller
right so I was thinking about this this morning.
The problem with PID is that it is purely reactive - it can't see into the future so to speak.
What are you guys thoughts on trying to implement a Futures-PID, ie we guess what boost we will be at in the future by what's happened in the past. That should hopefully reduce the amount of overboost spike we will see @ spoolup.
Anyways this is my code so far
edit - just updated the code with better Integral Windup, before I was limiting the output of IGAIN*I, now I am limiting I itself... Also added in code so I can flip between PID boost and Stock Boost... temporary for the timebeing but allows me to drive without fear of exploding something
The problem with PID is that it is purely reactive - it can't see into the future so to speak.
What are you guys thoughts on trying to implement a Futures-PID, ie we guess what boost we will be at in the future by what's happened in the past. That should hopefully reduce the amount of overboost spike we will see @ spoolup.
Anyways this is my code so far
Code:
sts.l pr, @-r15
mov.l r0, @-r15
mov.l r1, @-r15
mov.l r2, @-r15
mov.l r3, @-r15
mov.l r4, @-r15
mov.l r5, @-r15
mov.l r10, @-r15
! PSEUDO CODE
! MY_ERROR_PSI = target_boost - ECU_MAP_SENSOR
! OUTPUT = PGAIN*MY_ERROR_PSI + IGAIN2*MY_TOTAL_ERROR_PSI - DGAIN2*(MY_ERROR_PSI-MY_LAST_ERROR_PSI)
! MY_LAST_ERROR_PSI = MY_ERROR_PSI
mov.l (MY_BDEL_map_pointer), r0
mov.l @r0, r0
mov.l (alternate_bdel_map), r1
cmp/eq r0, r1
bf not_on_altmaps
mov.l (ECU_RPM_5), r0
mov.w @r0, r0
mov.w (min_rpm_for_boost_control), r1
cmp/hs r0, r1
bt rpm_for_boost_control_not_yet_met
mov.l (MY_DAVES_COUNTER), r0
mov.w @r0, r0
tst r0, r0
bf interval_countdown_not_zero
mov.w (interval), r1
mov.l (MY_DAVES_COUNTER), r0
mov.w r1, @r0
mov.l (axis_rpm_boost), r4
mov.l (Table_Lookup_Axis), r10
jsr @r10
nop
mov.l (MY_BDEL_map_pointer), r4
mov.l (Query_3D_Table), r10
jsr @r10
nop
mov r0, r1
mov.l (boost_control_load_offset), r0
mov.w @r0, r0
add r0, r1
mov.l (ECU_MAP_SENSOR), r0
mov.w @r0, r0
sub r0, r1 ! MY_ERROR_PSI = target_boost - ECU_MAP_SENSOR
mov.l (MY_ERROR_PSI), r0
mov.w r1, @r0
mov.w (p_gain_x10), r0
mul.l r1, r0
sts macl, r2 ! r2 = PGAIN*MY_ERROR_PSI
mov.l (MY_TOTAL_ERROR_PSI), r0
mov.w @r0, r0
add r0, r1
mov.w (min_i), r3
cmp/hs r1, r3
bt output_less_than_min_i
mov.w (max_i), r3
cmp/hs r3, r1
bf output_less_than_max_i
mov.w (max_i), r1
output_less_than_max_i:
bra output_i_range_check_done
nop
output_less_than_min_i:
mov.w (min_i), r1
output_i_range_check_done:
mov.l (MY_TOTAL_ERROR_PSI), r0
mov.w r1, @r0
mov.w (i_gain_x10), r0
mul.l r1, r0
sts macl, r3 ! r3 = IGAIN*MY_TOTAL_ERROR_PSI
mov.l (MY_LAST_ERROR_PSI), r0
mov.w @r0, r0
mov.l (MY_ERROR_PSI), r1
mov.w @r1, r1
sub r0, r1
mov.w (d_gain_x10), r0
mul.l r1, r0
sts macl, r4 ! r4 = DGAIN*(MY_ERROR_PSI-MY_LAST_ERROR_PSI)
add r3, r2
sub r4, r2
mov r2, r4
mov #0x0a, r5
mov.l (R4_DIV_R5_Into_R0), r10
jsr @r10
nop
mov.w (min_wgdc), r1
cmp/hs r0, r1
bt output_less_than_min_wgdc
mov.w (max_wgdc), r1
cmp/hs r1, r0
bf output_less_than_max_wgdc
mov.w (max_wgdc), r0
output_less_than_max_wgdc:
bra output_range_check_done
nop
output_less_than_min_wgdc:
mov.w (min_wgdc), r0
output_range_check_done:
mov #0x02, r1
mul.l r1, r0
sts macl, r0
mov.l (ECU_FINAL_WASTEGATE_DUTY), r1
mov.w r0, @r1
mov.l (MY_ERROR_PSI), r0
mov.w @r0, r0
mov.l (MY_LAST_ERROR_PSI), r1
mov.w r0, @r1
bra boost_control_done
nop
interval_countdown_not_zero:
mov.l (MY_DAVES_COUNTER), r0
mov.w @r0, r1
add #-0x1, r1
mov.w r1, @r0
bra boost_control_done
nop
rpm_for_boost_control_not_yet_met:
mov #0x0, r0
mov.l (MY_DAVES_COUNTER), r1
mov.w r0, @r1
mov.l (MY_LAST_ERROR_PSI), r1
mov.w r0, @r1
mov.l (MY_TOTAL_ERROR_PSI), r1
mov.w r0, @r1
mov.l (ECU_FINAL_WASTEGATE_DUTY), r1
mov.w r0, @r1
bra boost_control_done
nop
not_on_altmaps:
mov.l (Turbo_Control_Calcs), r10
jsr @r10
nop
boost_control_done:
mov.l @r15+, r10
mov.l @r15+, r5
mov.l @r15+, r4
mov.l @r15+, r3
mov.l @r15+, r2
mov.l @r15+, r1
mov.l @r15+, r0
lds.l @r15+, pr
rts
nop
.align 4
version: .float 1.0
ECU_MAP_SENSOR: .long 0xFFFF6ACC
ECU_FINAL_WASTEGATE_DUTY: .long 0xFFFF6EC6
ECU_RPM_5: .long 0xFFFF6AFE
MY_DAVES_COUNTER: .long 0xFFFF8440
MY_ERROR_PSI: .long 0xFFFF8442
MY_LAST_ERROR_PSI: .long 0xFFFF8444
MY_TOTAL_ERROR_PSI: .long 0xFFFF8446
MY_BDEL_map_pointer: .long 0xFFFF8438
R4_DIV_R5_Into_R0: .long 0x9FA
Query_3D_Table: .long 0xDE0
Table_Lookup_Axis: .long 0xCC6
boost_control_load_offset: .long 0x1670 ! OR Atmo base pressure :)
axis_rpm_boost: .long 0x732C
Turbo_Control_Calcs: .long 0x40110
alternate_bdel_map: .long 0x381e2
p_gain_x10: .word 2
i_gain_x10: .word 2
d_gain_x10: .word 1
min_i: .word 0
max_i: .word 1000
min_wgdc: .word 0
max_wgdc: .word 100
min_rpm_for_boost_control: .word 0x200
interval: .word 0x5
Last edited by tephra; May 18, 2008 at 11:58 PM.




