Skip to content

Commit f88d07b

Browse files
authored
Gernby's FF steering PR candidate (#97)
* Fixed feed-forward to consider steer_offset * fixed a small oops on types * Testing strategies for zero-crossing * Moved angle stat collection to latcontrol.py * First version that runs * added outbound interface for feed-forward table * First working * Added more metrics * Adjusted parameter timing * performance tweaks * Cleanup * untested tweaks * minor tweaks * many untested changes * going the other way with some things... * Best yet * cleaned up personalized "stuff" * more cleanup for general use * untested: minor adjustment to further reduce noise * Fixed defect in desired angle interpolation * some cleanup * reverted change to Ki * Reverted changes to manager.py * Added steering rate consideration to latcontrol * cleaned up for PR * Fixed merge * Testing approach when desired angle close to actual * trying rate-based feed-forward * added self-tuning parms for rate and angle FF * fixed trim logic, and persisted to flash * working amazingly well * decreased time lapse for angle-based FF adjust * many tweaks, self-tuning is a bitch * simulteneous dual mode feedforward working very well * added angular accelleration limit * added super awesome angular accel limit * non-specific save * switching directions again * oops * ugly code with amazing results * awesome, but untested after some cleanup * more cleanup * cleanup of the cleanup? Need to test * works amazingly well ... big O face * cleanup * I wish git was batter for cleanup * hopefully proper merge of merged fixes of merge * fixed an oops for non-bosch hondas * added steer rate to future state calculation * added actual acceleration to future projected state * fixed projected angle error for PIF * untested ... * added comments * completely UNtested * untested merge * battling shitty git * git is sucking giant monkey balls * Trying to figure out what git did... * still trying to un-git myself * Finally got it fixed, works awesome! * Hopefully not corrupted by git * hopefully fixed white space corruption * working again, hopefully resolved git issue * removed hard-coded actuator delay * Fixed steer_actuator_delay for pathplanner * Wiggle vision + resonant FF and error cancellation * increased projection factor and decreased accel_limit * enhanced torque logic to use accelerated rate * added smoothing of mpc delta values * trying different interpolation logic * added update to cur_state.delta * very smooth interpolation between delayed MPC updates * increased Kf for Pilot * much better interpolation plus Civic tweaks * tiny meaningless changes * Fixed honda interface values * removed steer_offset from pilot * Tweaked steer_offset list * Reverted a couple resonant parameters Reduced projection_factor and increased accel_limit * Enabled error correction * changed Kp for some * Works great... time to clean it up * reverted accelerated_angle_rate for pid updates * reverted visiond * Increased error correction * Reverted projection_factor * fixed projected_angle_steers for future statefixed projected_angle_steers for future state * Added simplified tuning parameters * last commit didn't have saved values * not working ... lane filtering * added calculated steer rate * updated toyota carstate * add dual-mode steer-rate alternating * reverted toyota carstate change * enabled auto-selection of steer_rate * removed extra variable * untested: cleaning up for PR * Fixed a cleanup issue * added comments in carstate * more cleanup * not quite ready * added vibration * need to test, but I think it's ready * Update carstate.py
1 parent 5dc6220 commit f88d07b

File tree

7 files changed

+158
-48
lines changed

7 files changed

+158
-48
lines changed

cereal/car.capnp

+5-1
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ struct CarParams {
327327
centerToFront @9 :Float32; # [m] GC distance to front axle
328328
steerRatio @10 :Float32; # [] ratio between front wheels and steering wheel angles
329329
steerRatioRear @11 :Float32; # [] rear steering ratio wrt front steering (usually 0)
330+
eonToFront @54 :Float32; # [m] distance from EON to front wheels
330331

331332
# things we can derive
332333
rotationalInertia @12 :Float32; # [kg*m2] body rotational inertia
@@ -341,7 +342,10 @@ struct CarParams {
341342
steerKpDEPRECATED @15 :Float32;
342343
steerKiDEPRECATED @16 :Float32;
343344
steerKf @25 :Float32;
344-
345+
steerReactance @51 :Float32;
346+
steerInductance @52 :Float32;
347+
steerResistance @53 :Float32;
348+
345349
# Kp and Ki for the longitudinal control
346350
longitudinalKpBP @36 :List(Float32);
347351
longitudinalKpV @37 :List(Float32);

selfdrive/car/honda/carcontroller.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def update(self, sendcan, enabled, CS, frame, actuators, \
147147

148148
# *** compute control surfaces ***
149149
BRAKE_MAX = 1024/4
150-
if CS.CP.carFingerprint in (CAR.ACURA_ILX):
150+
if CS.CP.carFingerprint in (CAR.ACCORD, CAR.ACCORD_15, CAR.ACCORDH, CAR.ACURA_ILX):
151151
STEER_MAX = 0xF00
152152
elif CS.CP.carFingerprint in (CAR.CRV, CAR.ACURA_RDX):
153153
STEER_MAX = 0x3e8 # CR-V only uses 12-bits and requires a lower value (max value from energee)

selfdrive/car/honda/interface.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,10 @@ def get_params(candidate, fingerprint):
186186
ret.steerKiBP, ret.steerKpBP = [[0.], [0.]]
187187

188188
ret.steerKf = 0.00006 # conservative feed-forward
189+
ret.steerReactance = 1.0
190+
ret.steerInductance = 1.0
191+
ret.steerResistance = 1.0
192+
ret.eonToFront = 0.5
189193

190194
if candidate == CAR.CIVIC:
191195
stop_and_go = True
@@ -226,7 +230,12 @@ def get_params(candidate, fingerprint):
226230
ret.centerToFront = ret.wheelbase * 0.39
227231
ret.steerRatio = 15.96 # 11.82 is spec end-to-end
228232
tire_stiffness_factor = 0.8467
229-
ret.steerKpV, ret.steerKiV = [[0.6], [0.18]]
233+
ret.steerReactance = 1.0
234+
ret.steerInductance = 1.0
235+
ret.steerResistance = 0.5
236+
ret.eonToFront = 1.0
237+
ret.steerKpV, ret.steerKiV = [[0.64], [0.192]]
238+
ret.steerKf = 0.000064
230239
ret.longitudinalKpBP = [0., 5., 35.]
231240
ret.longitudinalKpV = [1.2, 0.8, 0.5]
232241
ret.longitudinalKiBP = [0., 35.]

selfdrive/car/hyundai/carstate.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,9 @@ def update(self, cp, cp_cam):
184184
self.standstill = not self.v_wheel > 0.1
185185

186186
self.angle_steers = cp.vl["SAS11"]['SAS_Angle']
187-
self.angle_steers_rate = cp.vl["SAS11"]['SAS_Speed']
187+
188+
# The defined value in the DBC is always positive, so the calculated value needs to be used
189+
self.angle_steers_rate = 0.0 #cp.vl["SAS11"]['SAS_Speed']
188190
self.yaw_rate = cp.vl["ESP12"]['YAW_RATE']
189191
self.main_on = True
190192
self.left_blinker_on = cp.vl["CGW1"]['CF_Gway_TSigLHSw']

selfdrive/car/toyota/carstate.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,13 @@ def update(self, cp, cp_cam):
134134
self.standstill = not self.v_wheel > 0.001
135135

136136
self.angle_steers = cp.vl["STEER_ANGLE_SENSOR"]['STEER_ANGLE'] + cp.vl["STEER_ANGLE_SENSOR"]['STEER_FRACTION']
137-
self.angle_steers_rate = cp.vl["STEER_ANGLE_SENSOR"]['STEER_RATE']
137+
138+
# Only use the reported steer rate from some Toyotas, since others are very noisy
139+
if self.CP.carFingerprint in (CAR.RAV4, CAR.RAV4H, CAR.COROLLA):
140+
self.angle_steers_rate = cp.vl["STEER_ANGLE_SENSOR"]['STEER_RATE']
141+
else:
142+
self.angle_steers_rate = 0.0
143+
138144
can_gear = int(cp.vl["GEAR_PACKET"]['GEAR'])
139145
self.gear_shifter = parse_gear_shifter(can_gear, self.shifter_values)
140146
self.main_on = cp.vl["PCM_CRUISE_2"]['MAIN_ON']

selfdrive/controls/controlsd.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ def state_control(plan, CS, CP, state, events, v_cruise_kph, v_cruise_kph_last,
274274
v_cruise_kph, plan.vTarget, plan.vTargetFuture, plan.aTarget,
275275
CP, PL.lead_1)
276276
# Steering PID loop and lateral MPC
277-
actuators.steer, actuators.steerAngle = LaC.update(active, CS.vEgo, CS.steeringAngle,
277+
actuators.steer, actuators.steerAngle = LaC.update(active, CS.vEgo, CS.steeringAngle, CS.steeringRate,
278278
CS.steeringPressed, plan.dPoly, angle_offset, CP, VM, PL)
279279

280280
# Send a "steering required alert" if saturation count has reached the limit

selfdrive/controls/lib/latcontrol.py

+131-42
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
_DT_MPC = 0.05 # 20Hz
1313

1414

15-
def calc_states_after_delay(states, v_ego, steer_angle, curvature_factor, steer_ratio, delay):
16-
states[0].x = v_ego * delay
15+
def calc_states_after_delay(states, v_ego, steer_angle, curvature_factor, steer_ratio, delay, long_camera_offset):
16+
states[0].x = max(0.0, v_ego * delay + long_camera_offset)
1717
states[0].psi = v_ego * curvature_factor * math.radians(steer_angle) / steer_ratio * delay
1818
return states
1919

@@ -22,99 +22,188 @@ def get_steer_max(CP, v_ego):
2222
return interp(v_ego, CP.steerMaxBP, CP.steerMaxV)
2323

2424

25+
def apply_deadzone(angle, deadzone):
26+
if angle > deadzone:
27+
angle -= deadzone
28+
elif angle < -deadzone:
29+
angle += deadzone
30+
else:
31+
angle = 0.
32+
return angle
33+
34+
2535
class LatControl(object):
2636
def __init__(self, CP):
27-
self.pid = PIController((CP.steerKpBP, CP.steerKpV),
28-
(CP.steerKiBP, CP.steerKiV),
29-
k_f=CP.steerKf, pos_limit=1.0)
37+
38+
# Eliminate break-points, since they aren't needed (and would cause problems for resonance)
39+
KpV = [np.interp(25.0, CP.steerKpBP, CP.steerKpV) * CP.steerReactance]
40+
KiV = [np.interp(25.0, CP.steerKiBP, CP.steerKiV) * CP.steerReactance]
41+
Kf = CP.steerKf * CP.steerInductance
42+
self.pid = PIController(([0.], KpV),
43+
([0.], KiV),
44+
k_f=Kf, pos_limit=1.0)
3045
self.last_cloudlog_t = 0.0
3146
self.setup_mpc(CP.steerRateCost)
47+
self.smooth_factor = CP.steerInductance * 2.0 * CP.steerActuatorDelay / _DT # Multiplier for inductive component (feed forward)
48+
self.projection_factor = CP.steerReactance * 5.0 * _DT # Mutiplier for reactive component (PI)
49+
self.accel_limit = 2.0 / CP.steerResistance # Desired acceleration limit to prevent "whip steer" (resistive component)
50+
self.ff_angle_factor = 0.5 # Kf multiplier for angle-based feed forward
51+
self.ff_rate_factor = 5.0 # Kf multiplier for rate-based feed forward
52+
self.prev_angle_rate = 0
53+
self.feed_forward = 0.0
54+
self.last_mpc_ts = 0.0
55+
self.angle_steers_des = 0.0
56+
self.angle_steers_des_time = 0.0
57+
self.angle_steers_des_mpc = 0.0
58+
self.projected_angle_steers = 0.0
59+
self.steer_counter = 1.0
60+
self.steer_counter_prev = 0.0
61+
self.rough_steers_rate = 0.0
62+
self.prev_angle_steers = 0.0
63+
self.calculate_rate = True
3264

3365
def setup_mpc(self, steer_rate_cost):
3466
self.libmpc = libmpc_py.libmpc
3567
self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE, MPC_COST_LAT.HEADING, steer_rate_cost)
3668

3769
self.mpc_solution = libmpc_py.ffi.new("log_t *")
3870
self.cur_state = libmpc_py.ffi.new("state_t *")
71+
self.mpc_angles = [0.0, 0.0, 0.0]
72+
self.mpc_times = [0.0, 0.0, 0.0]
3973
self.mpc_updated = False
4074
self.mpc_nans = False
4175
self.cur_state[0].x = 0.0
4276
self.cur_state[0].y = 0.0
4377
self.cur_state[0].psi = 0.0
4478
self.cur_state[0].delta = 0.0
4579

46-
self.last_mpc_ts = 0.0
47-
self.angle_steers_des = 0.0
48-
self.angle_steers_des_mpc = 0.0
49-
self.angle_steers_des_prev = 0.0
50-
self.angle_steers_des_time = 0.0
51-
5280
def reset(self):
5381
self.pid.reset()
5482

55-
def update(self, active, v_ego, angle_steers, steer_override, d_poly, angle_offset, CP, VM, PL):
56-
cur_time = sec_since_boot()
83+
def update(self, active, v_ego, angle_steers, angle_rate, steer_override, d_poly, angle_offset, CP, VM, PL):
5784
self.mpc_updated = False
85+
86+
if angle_rate == 0.0 and self.calculate_rate:
87+
if angle_steers != self.prev_angle_steers:
88+
self.steer_counter_prev = self.steer_counter
89+
self.rough_steers_rate = (self.rough_steers_rate + 100.0 * (angle_steers - self.prev_angle_steers) / self.steer_counter_prev) / 2.0
90+
self.steer_counter = 0.0
91+
elif self.steer_counter >= self.steer_counter_prev:
92+
self.rough_steers_rate = (self.steer_counter * self.rough_steers_rate) / (self.steer_counter + 1.0)
93+
self.steer_counter += 1.0
94+
angle_rate = self.rough_steers_rate
95+
96+
# Don't use accelerated rate unless it's from CAN
97+
accelerated_angle_rate = angle_rate
98+
else:
99+
# If non-zero angle_rate is provided, use it instead
100+
self.calculate_rate = False
101+
# Use steering rate from the last 2 samples to estimate acceleration for a likely future steering rate
102+
accelerated_angle_rate = 2.0 * angle_rate - self.prev_angle_rate
103+
58104
# TODO: this creates issues in replay when rewinding time: mpc won't run
59105
if self.last_mpc_ts < PL.last_md_ts:
60106
self.last_mpc_ts = PL.last_md_ts
61-
self.angle_steers_des_prev = self.angle_steers_des_mpc
62-
107+
cur_time = sec_since_boot()
108+
mpc_time = float(self.last_mpc_ts / 1000000000.0)
63109
curvature_factor = VM.curvature_factor(v_ego)
64110

111+
# Determine future angle steers using steer rate
112+
projected_angle_steers = float(angle_steers) + CP.steerActuatorDelay * float(accelerated_angle_rate)
113+
114+
# Determine a proper delay time that includes the model's variable processing time
115+
plan_age = _DT_MPC + cur_time - mpc_time
116+
total_delay = CP.steerActuatorDelay + plan_age
117+
65118
l_poly = libmpc_py.ffi.new("double[4]", list(PL.PP.l_poly))
66119
r_poly = libmpc_py.ffi.new("double[4]", list(PL.PP.r_poly))
67120
p_poly = libmpc_py.ffi.new("double[4]", list(PL.PP.p_poly))
68121

69-
# account for actuation delay
70-
self.cur_state = calc_states_after_delay(self.cur_state, v_ego, angle_steers, curvature_factor, CP.steerRatio, CP.steerActuatorDelay)
122+
# account for actuation delay and the age of the plan
123+
self.cur_state = calc_states_after_delay(self.cur_state, v_ego, projected_angle_steers, curvature_factor,
124+
CP.steerRatio, total_delay, CP.eonToFront)
71125

72126
v_ego_mpc = max(v_ego, 5.0) # avoid mpc roughness due to low speed
73127
self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
74128
l_poly, r_poly, p_poly,
75129
PL.PP.l_prob, PL.PP.r_prob, PL.PP.p_prob, curvature_factor, v_ego_mpc, PL.PP.lane_width)
76130

77-
# reset to current steer angle if not active or overriding
78-
if active:
79-
delta_desired = self.mpc_solution[0].delta[1]
80-
else:
81-
delta_desired = math.radians(angle_steers - angle_offset) / CP.steerRatio
82-
83-
self.cur_state[0].delta = delta_desired
84-
85-
self.angle_steers_des_mpc = float(math.degrees(delta_desired * CP.steerRatio) + angle_offset)
86-
self.angle_steers_des_time = cur_time
87131
self.mpc_updated = True
88132

89133
# Check for infeasable MPC solution
90134
self.mpc_nans = np.any(np.isnan(list(self.mpc_solution[0].delta)))
91-
t = sec_since_boot()
92-
if self.mpc_nans:
135+
if not self.mpc_nans:
136+
self.mpc_angles = [self.angle_steers_des,
137+
float(math.degrees(self.mpc_solution[0].delta[1] * CP.steerRatio) + angle_offset),
138+
float(math.degrees(self.mpc_solution[0].delta[2] * CP.steerRatio) + angle_offset)]
139+
140+
self.mpc_times = [self.angle_steers_des_time,
141+
mpc_time + _DT_MPC,
142+
mpc_time + _DT_MPC + _DT_MPC]
143+
144+
self.angle_steers_des_mpc = self.mpc_angles[1]
145+
else:
93146
self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE, MPC_COST_LAT.HEADING, CP.steerRateCost)
94147
self.cur_state[0].delta = math.radians(angle_steers) / CP.steerRatio
95148

96-
if t > self.last_cloudlog_t + 5.0:
97-
self.last_cloudlog_t = t
149+
if cur_time > self.last_cloudlog_t + 5.0:
150+
self.last_cloudlog_t = cur_time
98151
cloudlog.warning("Lateral mpc - nan: True")
99152

153+
cur_time = sec_since_boot()
154+
self.angle_steers_des_time = cur_time
155+
100156
if v_ego < 0.3 or not active:
101157
output_steer = 0.0
158+
self.feed_forward = 0.0
102159
self.pid.reset()
160+
self.angle_steers_des = angle_steers
161+
self.cur_state[0].delta = math.radians(angle_steers - angle_offset) / CP.steerRatio
103162
else:
104-
# TODO: ideally we should interp, but for tuning reasons we keep the mpc solution
105-
# constant for 0.05s.
106-
#dt = min(cur_time - self.angle_steers_des_time, _DT_MPC + _DT) + _DT # no greater than dt mpc + dt, to prevent too high extraps
107-
#self.angle_steers_des = self.angle_steers_des_prev + (dt / _DT_MPC) * (self.angle_steers_des_mpc - self.angle_steers_des_prev)
108-
self.angle_steers_des = self.angle_steers_des_mpc
163+
# Interpolate desired angle between MPC updates
164+
self.angle_steers_des = np.interp(cur_time, self.mpc_times, self.mpc_angles)
165+
self.angle_steers_des_time = cur_time
166+
self.cur_state[0].delta = math.radians(self.angle_steers_des - angle_offset) / CP.steerRatio
167+
168+
# Determine the target steer rate for desired angle, but prevent the acceleration limit from being exceeded
169+
# Restricting the steer rate creates the resistive component needed for resonance
170+
restricted_steer_rate = np.clip(self.angle_steers_des - float(angle_steers) , float(accelerated_angle_rate) - self.accel_limit,
171+
float(accelerated_angle_rate) + self.accel_limit)
172+
173+
# Determine projected desired angle that is within the acceleration limit (prevent the steering wheel from jerking)
174+
projected_angle_steers_des = self.angle_steers_des + self.projection_factor * restricted_steer_rate
175+
176+
# Determine future angle steers using accellerated steer rate
177+
self.projected_angle_steers = float(angle_steers) + self.projection_factor * float(accelerated_angle_rate)
178+
109179
steers_max = get_steer_max(CP, v_ego)
110180
self.pid.pos_limit = steers_max
111181
self.pid.neg_limit = -steers_max
112-
steer_feedforward = self.angle_steers_des # feedforward desired angle
113-
if CP.steerControlType == car.CarParams.SteerControlType.torque:
114-
steer_feedforward *= v_ego**2 # proportional to realigning tire momentum (~ lateral accel)
115182
deadzone = 0.0
116-
output_steer = self.pid.update(self.angle_steers_des, angle_steers, check_saturation=(v_ego > 10), override=steer_override,
117-
feedforward=steer_feedforward, speed=v_ego, deadzone=deadzone)
183+
184+
if CP.steerControlType == car.CarParams.SteerControlType.torque:
185+
# Decide which feed forward mode should be used (angle or rate). Use more dominant mode, but only if conditions are met
186+
# Spread feed forward out over a period of time to make it inductive (for resonance)
187+
if abs(self.ff_rate_factor * float(restricted_steer_rate)) > abs(self.ff_angle_factor * float(self.angle_steers_des) - float(angle_offset)) - 0.5 \
188+
and (abs(float(restricted_steer_rate)) > abs(accelerated_angle_rate) or (float(restricted_steer_rate) < 0) != (accelerated_angle_rate < 0)) \
189+
and (float(restricted_steer_rate) < 0) == (float(self.angle_steers_des) - float(angle_offset) - 0.5 < 0):
190+
self.feed_forward = (((self.smooth_factor - 1.) * self.feed_forward) + self.ff_rate_factor * v_ego**2 * float(restricted_steer_rate)) / self.smooth_factor
191+
elif abs(self.angle_steers_des - float(angle_offset)) > 0.5:
192+
self.feed_forward = (((self.smooth_factor - 1.) * self.feed_forward) + self.ff_angle_factor * v_ego**2 \
193+
* float(apply_deadzone(float(self.angle_steers_des) - float(angle_offset), 0.5))) / self.smooth_factor
194+
else:
195+
self.feed_forward = (((self.smooth_factor - 1.) * self.feed_forward) + 0.0) / self.smooth_factor
196+
197+
# Use projected desired and actual angles instead of "current" values, in order to make PI more reactive (for resonance)
198+
output_steer = self.pid.update(projected_angle_steers_des, self.projected_angle_steers, check_saturation=(v_ego > 10),
199+
override=steer_override, feedforward=self.feed_forward, speed=v_ego, deadzone=deadzone)
118200

119201
self.sat_flag = self.pid.saturated
120-
return output_steer, float(self.angle_steers_des)
202+
self.prev_angle_rate = angle_rate
203+
self.prev_angle_steers = angle_steers
204+
205+
# return MPC angle in the unused output (for ALCA)
206+
if CP.steerControlType == car.CarParams.SteerControlType.torque:
207+
return output_steer, self.mpc_angles[1]
208+
else:
209+
return self.mpc_angles[1], float(self.angle_steers_des)

0 commit comments

Comments
 (0)