速度环的后级,再串上一个电流环,以前级的输出 (这里指速度环的输出),作为后级的输入 (这里指作为电流环的输入),最终后级 (电流环) 的输出最终实现在执行机构上, 以形成双反馈控 制的效果。
在串级 PID 控制中,最外环一般选择期望控制的参数的环节,例如对应速度快慢的速度环、位置的位置环、电流大小的电流环大小,本代码的选择位置环作为最外环,位置作为控制量,期望控制电机实际位置。
/**
* @brief 电机位置式 PID 控制实现(定时调用)
* @param 无
* @retval 无
* 电机的输出位置作为控制目标,所以就以位置环作为外环
*/
void motor_pid_control(void)
{
static uint32_t louter_ring_timer = 0; // 外环环周期(电流环计算周期为定时器周期T,速度环为2T,位置环为3T)
int32_t actual_current = get_curr_val(); // 读取当前电流值
if (actual_current > TARGET_CURRENT_MAX)
{
actual_current = TARGET_CURRENT_MAX;
}
if (is_motor_en == 1) // 电机在使能状态下才进行控制处理
{
static int32_t Capture_Count = 0; // 当前时刻总计数值
static int32_t Last_Count = 0; // 上一时刻总计数值
float cont_val = 0; // 当前控制值
/* 当前时刻总计数值 = 计数器值 + 计数溢出次数 * ENCODER_TIM_PERIOD */
Capture_Count = __HAL_TIM_GET_COUNTER(&TIM_EncoderHandle) + (Encoder_Overflow_Count * ENCODER_TIM_PERIOD);
/* 位置环计算 */
if (louter_ring_timer++ % 3 == 0)
{
cont_val = location_pid_realize(&pid_location, Capture_Count); // 进行 PID 计算
/* 目标速度上限处理 */
if (cont_val > TARGET_SPEED_MAX)
{
cont_val = TARGET_SPEED_MAX;
}
else if (cont_val < -TARGET_SPEED_MAX)
{
cont_val = -TARGET_SPEED_MAX;
}
/*******************************
将位置环的输出给速度环
********************************/
set_pid_target(&pid_speed, cont_val); // 设定速度的目标值
#if defined(PID_ASSISTANT_EN)
int32_t temp = cont_val;
set_computer_value(SEND_TARGET_CMD, CURVES_CH2, &temp, 1); // 给通道 2 发送目标值
#endif
}
/* 速度环计算 */
static int32_t actual_speed = 0; // 实际测得速度
if (louter_ring_timer % 2 == 0)
{
/* 转轴转速 = 单位时间内的计数值 / 编码器总分辨率 * 时间系数 */
actual_speed = ((float)(Capture_Count - Last_Count) / ENCODER_TOTAL_RESOLUTION / REDUCTION_RATIO) / (GET_BASIC_TIM_PERIOD() * 2 / 1000.0 / 60.0);
/* 记录当前总计数值,供下一时刻计算使用 */
Last_Count = Capture_Count;
cont_val = speed_pid_realize(&pid_speed, actual_speed); // 进行 PID 计算
if (cont_val > 0) // 判断电机方向
{
set_motor_direction(MOTOR_FWD);
}
else
{
cont_val = -cont_val;
set_motor_direction(MOTOR_REV);
}
cont_val = (cont_val > TARGET_CURRENT_MAX) ? TARGET_CURRENT_MAX : cont_val; // 电流上限处理
/*******************************
将 速度 环的输出给 电流 环
********************************/
set_pid_target(&pid_curr, cont_val); // 设定电流的目标值
#if defined(PID_ASSISTANT_EN)
int32_t temp = cont_val;
set_computer_value(SEND_TARGET_CMD, CURVES_CH3, &temp, 1); // 给通道 3 发送目标值
#endif
}
/* 电流环计算 */
cont_val = curr_pid_realize(&pid_curr, actual_current); // 进行 PID 计算
if (cont_val < 0)
{
cont_val = 0; // 下限处理
}
else if (cont_val > PWM_MAX_PERIOD_COUNT)
{
cont_val = PWM_MAX_PERIOD_COUNT; // 速度上限处理
}
/*******************************
将 电流 环的输出直接作用给 被控对象
********************************/
set_motor_speed(cont_val); // 设置 PWM 占空比
#if defined(PID_ASSISTANT_EN)
set_computer_value(SEND_FACT_CMD, CURVES_CH1, &Capture_Count, 1); // 给通道 1 发送实际值
// set_computer_value(SEND_FACT_CMD, CURVES_CH2, &actual_speed, 1); // 给通道 2 发送实际值
// set_computer_value(SEND_FACT_CMD, CURVES_CH3, &actual_current, 1); // 给通道 3 发送实际值
#else
printf("1.电流:实际值:%d. 目标值:%.0f.\n", Capture_Count, get_pid_target(&pid_location)); // 打印实际值和目标值
#endif
}
}
闭环死区,是指执行机构的最小控制量,无法满足控制需求产生的
积分饱和的处理。积分饱和,就是执行机构达到极限输出能力了,仍无法到达目标值,在很长一
段时间内无法消除静差造成的。简单举例,就是电机满功率运行,仍达不到期望转速,在一段时
间内没有到达目标值,这时候
PID
的积分项累计了很大的数值,如果这时候到达了目标值或者重
新设定了目标值,由于积分由于累计的误差很大,系统并不能马上稳定到目标值,并会造成严重
的超调或失调的现象。解决办法有很多,代码中使用了积分分离的方法,在累计误差大于一定值
后去掉积分项的作用。
由于编码器精度原因,当实际值和目标值的偏差小于编码区能测量得到的最小精度时,就认为目
标值与实际值没有偏差