You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
133 lines
4.7 KiB
133 lines
4.7 KiB
import math
|
|
import numpy as np
|
|
from common.numpy_fast import clip, interp
|
|
from selfdrive.config import Conversions as CV
|
|
|
|
_K_CURV_V = [1., 0.6]
|
|
_K_CURV_BP = [0., 0.002]
|
|
|
|
def calc_d_lookahead(v_ego, d_poly):
|
|
#*** this function computes how far too look for lateral control
|
|
# howfar we look ahead is function of speed and how much curvy is the path
|
|
offset_lookahead = 1.
|
|
k_lookahead = 7.
|
|
# integrate abs value of second derivative of poly to get a measure of path curvature
|
|
pts_len = 50. # m
|
|
if len(d_poly)>0:
|
|
pts = np.polyval([6*d_poly[0], 2*d_poly[1]], np.arange(0, pts_len))
|
|
else:
|
|
pts = 0.
|
|
curv = np.sum(np.abs(pts))/pts_len
|
|
|
|
k_curv = interp(curv, _K_CURV_BP, _K_CURV_V)
|
|
|
|
# sqrt on speed is needed to keep, for a given curvature, the y_des
|
|
# proportional to speed. Indeed, y_des is prop to d_lookahead^2
|
|
# 36m at 25m/s
|
|
d_lookahead = offset_lookahead + math.sqrt(max(v_ego, 0)) * k_lookahead * k_curv
|
|
return d_lookahead
|
|
|
|
def calc_lookahead_offset(v_ego, angle_steers, d_lookahead, VM, angle_offset):
|
|
#*** this function returns the lateral offset given the steering angle, speed and the lookahead distance
|
|
sa = (angle_steers - angle_offset) * CV.DEG_TO_RAD
|
|
curvature = VM.calc_curvature(sa, v_ego)
|
|
# clip is to avoid arcsin NaNs due to too sharp turns
|
|
y_actual = d_lookahead * np.tan(np.arcsin(np.clip(d_lookahead * curvature, -0.999, 0.999))/2.)
|
|
return y_actual, curvature
|
|
|
|
def calc_desired_steer_angle(v_ego, y_des, d_lookahead, VM, angle_offset):
|
|
# inverse of the above function
|
|
curvature = np.sin(np.arctan(y_des / d_lookahead) * 2.) / d_lookahead
|
|
steer_des = VM.get_steer_from_curvature(curvature, v_ego) * CV.RAD_TO_DEG + angle_offset
|
|
return steer_des, curvature
|
|
|
|
def pid_lateral_control(v_ego, sa_actual, sa_des, Ui_steer, steer_max,
|
|
steer_override, sat_count, enabled, Kp, Ki, rate):
|
|
|
|
sat_count_rate = 1./rate
|
|
sat_count_limit = 0.8 # after 0.8s of continuous saturation, an alert will be sent
|
|
|
|
error_steer = sa_des - sa_actual
|
|
Ui_unwind_speed = 0.3/rate #.3 per second
|
|
|
|
Up_steer = error_steer*Kp
|
|
Ui_steer_new = Ui_steer + error_steer*Ki * 1./rate
|
|
output_steer_new = Ui_steer_new + Up_steer
|
|
|
|
# Anti-wind up for integrator: do not integrate if we are against the steer limits
|
|
if (
|
|
(error_steer >= 0. and (output_steer_new < steer_max or Ui_steer < 0)) or
|
|
(error_steer <= 0. and
|
|
(output_steer_new > -steer_max or Ui_steer > 0))) and not steer_override:
|
|
#update integrator
|
|
Ui_steer = Ui_steer_new
|
|
# unwind integrator if driver is maneuvering the steering wheel
|
|
elif steer_override:
|
|
Ui_steer -= Ui_unwind_speed * np.sign(Ui_steer)
|
|
|
|
# still, intergral term should not be bigger then limits
|
|
Ui_steer = clip(Ui_steer, -steer_max, steer_max)
|
|
|
|
output_steer = Up_steer + Ui_steer
|
|
|
|
# don't run steer control if at very low speed
|
|
if v_ego < 0.3 or not enabled:
|
|
output_steer = 0.
|
|
Ui_steer = 0.
|
|
|
|
# useful to know if control is against the limit
|
|
lateral_control_sat = False
|
|
if abs(output_steer) > steer_max:
|
|
lateral_control_sat = True
|
|
|
|
output_steer = clip(output_steer, -steer_max, steer_max)
|
|
|
|
# if lateral control is saturated for a certain period of time, send an alert for taking control of the car
|
|
# wind
|
|
if lateral_control_sat and not steer_override and v_ego > 10 and abs(error_steer) > 0.1:
|
|
sat_count += sat_count_rate
|
|
# unwind
|
|
else:
|
|
sat_count -= sat_count_rate
|
|
|
|
sat_flag = False
|
|
if sat_count >= sat_count_limit:
|
|
sat_flag = True
|
|
|
|
sat_count = clip(sat_count, 0, 1)
|
|
|
|
return output_steer, Up_steer, Ui_steer, lateral_control_sat, sat_count, sat_flag
|
|
|
|
class LatControl(object):
|
|
def __init__(self):
|
|
self.Up_steer = 0.
|
|
self.sat_count = 0
|
|
self.y_des = 0.0
|
|
self.lateral_control_sat = False
|
|
self.Ui_steer = 0.
|
|
self.reset()
|
|
|
|
def reset(self):
|
|
self.Ui_steer = 0.
|
|
|
|
def update(self, enabled, v_ego, angle_steers, steer_override, d_poly, angle_offset, VM):
|
|
rate = 100
|
|
|
|
steer_max = 1.0
|
|
|
|
# how far we look ahead is function of speed and desired path
|
|
d_lookahead = calc_d_lookahead(v_ego, d_poly)
|
|
|
|
# desired lookahead offset
|
|
self.y_des = np.polyval(d_poly, d_lookahead)
|
|
|
|
# calculate actual offset at the lookahead point
|
|
self.angle_steers_des, _ = calc_desired_steer_angle(v_ego, self.y_des,
|
|
d_lookahead, VM, angle_offset)
|
|
|
|
output_steer, self.Up_steer, self.Ui_steer, self.lateral_control_sat, self.sat_count, sat_flag = pid_lateral_control(
|
|
v_ego, angle_steers, self.angle_steers_des, self.Ui_steer, steer_max,
|
|
steer_override, self.sat_count, enabled, VM.CP.steerKp, VM.CP.steerKi, rate)
|
|
|
|
final_steer = clip(output_steer, -steer_max, steer_max)
|
|
return final_steer, sat_flag
|
|
|