@@ -71,6 +71,57 @@ static bool pbio_control_status_test(pbio_control_t *ctl, pbio_control_status_fl
7171 return ctl -> status & flag ;
7272}
7373
74+ static int32_t pbio_control_get_pid_kp (pbio_control_settings_t * settings , int32_t position_error , int32_t target_error , int32_t abs_command_speed ) {
75+
76+ // Reduced kp values are only needed for some motors under slow speed
77+ // conditions. For everything else, use the default kp value.
78+ if (abs_command_speed > settings -> pid_kp_low_speed_threshold || position_error == 0 ) {
79+ return settings -> pid_kp ;
80+ }
81+
82+ // We only need a positive kp value, so work with absolute values
83+ // throughout this function to make things easy to follow.
84+ position_error = pbio_int_math_abs (position_error );
85+ target_error = pbio_int_math_abs (target_error );
86+
87+ // Lowest kp value, used when steadily turning at slow speed.
88+ const int32_t kp_low = settings -> pid_kp * settings -> pid_kp_low_pct / 100 ;
89+
90+ // Get equivalent kp value to produce a piece-wise affine (pwa) feedback in the
91+ // position error. It grows slower at first, and then at the configured rate.
92+ const int32_t kp_pwa = position_error <= settings -> pid_kp_low_error_threshold ?
93+ // For small errors, feedback is linear in low kp value.
94+ kp_low :
95+ // Above the threshold, feedback grows with the default gain.
96+ settings -> pid_kp - settings -> pid_kp_low_error_threshold * (settings -> pid_kp - kp_low ) / position_error ;
97+
98+ // Proportional control saturates where the error leads to maximum actuation.
99+ // For errors any smaller than that,
100+ const int32_t saturation_lower = pbio_control_settings_div_by_gain (settings -> actuation_max , settings -> pid_kp );
101+
102+ // Further away from the target, we can use the reduced value and still
103+ // guarantee maximum actuation, to avoid getting stuck.
104+ const int32_t saturation_upper = saturation_lower * 100 / settings -> pid_kp_low_pct ;
105+
106+ // Get the kp value as constrained by the condition to use maxium actuation
107+ // close to the target to guarantee that we can always get there.
108+ int32_t kp_target ;
109+ if (target_error < saturation_lower ) {
110+ kp_target = settings -> pid_kp ;
111+ } else if (target_error > saturation_upper ) {
112+ kp_target = kp_low ;
113+ } else {
114+ // In between, we gradually shift towards the higher value as we get
115+ // closer to the final target to avoid a sudden transition.
116+ kp_target = kp_low + settings -> pid_kp *
117+ (100 - settings -> pid_kp_low_pct ) * (saturation_upper - target_error ) /
118+ (saturation_upper - saturation_lower ) / 100 ;
119+ }
120+
121+ // The most constrained objective is obtained by taking the highest value.
122+ return pbio_int_math_max (kp_pwa , kp_target );
123+ }
124+
74125/**
75126 * Updates the PID controller state to calculate the next actuation step.
76127 *
@@ -98,23 +149,26 @@ void pbio_control_update(pbio_control_t *ctl, uint32_t time_now, pbio_control_st
98149 // Calculate integral control errors, depending on control type.
99150 int32_t integral_error ;
100151 int32_t position_error_used ;
152+ int32_t target_error ;
101153 if (pbio_control_type_is_position (ctl )) {
102-
154+ // Get error to final position, used below to adjust controls near end.
155+ target_error = pbio_angle_diff_mdeg (& ref_end .position , & state -> position );
103156 // Update count integral error and get current error state
104- int32_t target_error = pbio_angle_diff_mdeg (& ref_end .position , & state -> position );
105157 integral_error = pbio_position_integrator_update (& ctl -> position_integrator , position_error , target_error );
106-
107158 // For position control, the proportional term is the real position error.
108159 position_error_used = position_error ;
109160 } else {
110161 // For time/speed based commands, the main error is speed. It integrates into a quantity with unit of position.
111162 // There is no count integral control, because we do not need a second order integrator for speed control.
112163 position_error_used = pbio_speed_integrator_get_error (& ctl -> speed_integrator , position_error );
113164 integral_error = 0 ;
165+ // Speed maneuvers don't have a specific angle end target, so the error is infinite.
166+ target_error = INT32_MAX ;
114167 }
115168
116169 // Corresponding PID control signal
117- int32_t torque_proportional = pbio_control_settings_mul_by_gain (position_error_used , ctl -> settings .pid_kp );
170+ int32_t pid_kp = pbio_control_get_pid_kp (& ctl -> settings , position_error , target_error , pbio_trajectory_get_abs_command_speed (& ctl -> trajectory ));
171+ int32_t torque_proportional = pbio_control_settings_mul_by_gain (position_error_used , pid_kp );
118172 int32_t torque_derivative = pbio_control_settings_mul_by_gain (speed_error , ctl -> settings .pid_kd );
119173 int32_t torque_integral = pbio_control_settings_mul_by_gain (integral_error , ctl -> settings .pid_ki );
120174
0 commit comments