PID控制算法实战¶
学习目标¶
完成本教程后,你将能够:
- 理解PID控制器的工作原理和数学模型
- 掌握比例(P)、积分(I)、微分(D)三个环节的作用
- 学会PID参数整定的多种方法
- 实现数字PID控制器
- 掌握抗积分饱和和微分滤波技术
- 应用PID控制器实现电机速度控制
前置要求¶
在开始本教程之前,你需要:
知识要求: - 理解闭环控制系统的基本概念 - 掌握PWM技术和电机控制基础 - 了解基本的数学运算(加减乘除) - 熟悉C语言编程
技能要求: - 能够使用STM32或Arduino开发环境 - 会使用串口调试工具 - 能够编写定时器中断程序 - 具备基本的调试能力
准备工作¶
硬件准备¶
| 名称 | 数量 | 说明 | 参考价格 |
|---|---|---|---|
| 开发板 | 1 | STM32F103或Arduino Uno | ¥30-50 |
| 直流电机 | 1 | 带编码器,12V | ¥40-80 |
| 电机驱动模块 | 1 | L298N或TB6612 | ¥10-20 |
| 电源 | 1 | 12V,2A以上 | ¥20-40 |
| 编码器 | 1 | 如电机自带则不需要 | ¥15-30 |
| 杜邦线 | 若干 | 公对公、公对母 | ¥5 |
软件准备¶
STM32平台: - STM32CubeIDE 或 Keil MDK - ST-Link驱动程序 - 串口调试助手
Arduino平台: - Arduino IDE 1.8.x 或 2.x - USB驱动程序 - 串口监视器
理论基础¶
什么是PID控制?¶
PID(Proportional-Integral-Derivative)控制器是工业控制中最常用的控制算法。它通过计算误差的比例、积分和微分来产生控制输出,使系统输出跟踪期望值。
基本框图:
设定值(Setpoint)
│
├──────> [比例P] ──┐
│ │
├──────> [积分I] ──┼──> [求和] ──> 控制输出
│ │
└──────> [微分D] ──┘
│
└──────> 执行器 ──> 被控对象
│
└──> 反馈测量
PID数学模型¶
连续时间PID方程:
u(t) = Kp × e(t) + Ki × ∫e(t)dt + Kd × de(t)/dt
其中:
u(t) = 控制输出
e(t) = 误差 = 设定值 - 实际值
Kp = 比例系数
Ki = 积分系数
Kd = 微分系数
离散时间PID方程(用于数字实现):
u(k) = Kp × e(k) + Ki × Σe(k) + Kd × [e(k) - e(k-1)]
其中:
k = 当前采样时刻
e(k) = 当前误差
Σe(k) = 误差累积和
e(k-1) = 上一次误差
三个环节的作用¶
1. 比例环节(P)
- 作用:根据当前误差大小产生控制作用
- 特点:响应快,但存在稳态误差
- 效果:Kp越大,响应越快,但容易超调和振荡
2. 积分环节(I)
- 作用:消除稳态误差
- 特点:累积历史误差,直到误差为零
- 效果:Ki越大,消除误差越快,但容易积分饱和
3. 微分环节(D)
- 作用:预测误差变化趋势,提前调节
- 特点:对误差变化率敏感
- 效果:Kd越大,超调越小,但对噪声敏感
PID控制效果对比¶
| 控制器类型 | 响应速度 | 稳态误差 | 超调 | 稳定性 | 适用场景 |
|---|---|---|---|---|---|
| P | 快 | 有 | 中等 | 较好 | 简单系统 |
| PI | 中等 | 无 | 较大 | 好 | 大多数系统 |
| PD | 快 | 有 | 小 | 较好 | 快速响应系统 |
| PID | 快 | 无 | 可调 | 最好 | 复杂系统 |
步骤1:基础PID控制器实现¶
1.1 PID结构体定义¶
// PID控制器结构体
typedef struct {
// PID参数
float Kp; // 比例系数
float Ki; // 积分系数
float Kd; // 微分系数
// 中间变量
float setpoint; // 设定值(目标值)
float measured; // 测量值(实际值)
float error; // 当前误差
float last_error; // 上次误差
float integral; // 积分累积
float derivative; // 微分值
// 输出限制
float output_min; // 输出最小值
float output_max; // 输出最大值
float output; // 控制输出
// 采样时间
float sample_time; // 采样周期(秒)
} PID_Controller;
// 初始化PID控制器
void PID_Init(PID_Controller *pid, float kp, float ki, float kd) {
pid->Kp = kp;
pid->Ki = ki;
pid->Kd = kd;
pid->setpoint = 0.0f;
pid->measured = 0.0f;
pid->error = 0.0f;
pid->last_error = 0.0f;
pid->integral = 0.0f;
pid->derivative = 0.0f;
pid->output_min = 0.0f;
pid->output_max = 100.0f;
pid->output = 0.0f;
pid->sample_time = 0.01f; // 默认10ms
}
// 设置输出限制
void PID_SetOutputLimits(PID_Controller *pid, float min, float max) {
pid->output_min = min;
pid->output_max = max;
}
// 设置采样时间
void PID_SetSampleTime(PID_Controller *pid, float sample_time) {
pid->sample_time = sample_time;
}
1.2 基础PID计算函数¶
// PID计算函数(位置式PID)
float PID_Compute(PID_Controller *pid, float setpoint, float measured) {
// 更新设定值和测量值
pid->setpoint = setpoint;
pid->measured = measured;
// 计算误差
pid->error = pid->setpoint - pid->measured;
// 比例项
float p_term = pid->Kp * pid->error;
// 积分项
pid->integral += pid->error * pid->sample_time;
float i_term = pid->Ki * pid->integral;
// 微分项
pid->derivative = (pid->error - pid->last_error) / pid->sample_time;
float d_term = pid->Kd * pid->derivative;
// PID输出
pid->output = p_term + i_term + d_term;
// 输出限幅
if (pid->output > pid->output_max) {
pid->output = pid->output_max;
} else if (pid->output < pid->output_min) {
pid->output = pid->output_min;
}
// 保存当前误差
pid->last_error = pid->error;
return pid->output;
}
// 重置PID控制器
void PID_Reset(PID_Controller *pid) {
pid->error = 0.0f;
pid->last_error = 0.0f;
pid->integral = 0.0f;
pid->derivative = 0.0f;
pid->output = 0.0f;
}
1.3 测试代码¶
// 测试PID控制器
void Test_PID_Basic(void) {
PID_Controller pid;
// 初始化PID(Kp=2.0, Ki=0.5, Kd=0.1)
PID_Init(&pid, 2.0f, 0.5f, 0.1f);
PID_SetOutputLimits(&pid, 0.0f, 100.0f);
PID_SetSampleTime(&pid, 0.01f); // 10ms
// 模拟控制过程
float setpoint = 100.0f; // 目标值
float measured = 0.0f; // 当前值
printf("Time(s)\tSetpoint\tMeasured\tError\tOutput\n");
for (int i = 0; i < 200; i++) {
// 计算PID输出
float output = PID_Compute(&pid, setpoint, measured);
// 模拟系统响应(简化的一阶系统)
measured += (output - measured) * 0.1f;
// 每10次打印一次
if (i % 10 == 0) {
printf("%.2f\t%.1f\t%.1f\t%.1f\t%.1f\n",
i * 0.01f, setpoint, measured, pid.error, output);
}
HAL_Delay(10); // 10ms采样周期
}
}
步骤2:抗积分饱和¶
2.1 积分饱和问题¶
当系统存在饱和限制时(如PWM占空比0-100%),积分项可能会持续累积,导致: - 超调增大 - 调节时间延长 - 系统响应变慢
示例:
2.2 积分限幅法¶
最简单的方法是限制积分项的范围:
// 带积分限幅的PID计算
float PID_Compute_AntiWindup(PID_Controller *pid, float setpoint, float measured) {
pid->setpoint = setpoint;
pid->measured = measured;
pid->error = pid->setpoint - pid->measured;
// 比例项
float p_term = pid->Kp * pid->error;
// 积分项(带限幅)
pid->integral += pid->error * pid->sample_time;
// 计算积分限幅值
float integral_max = (pid->output_max - pid->output_min) / pid->Ki;
// 限制积分项
if (pid->integral > integral_max) {
pid->integral = integral_max;
} else if (pid->integral < -integral_max) {
pid->integral = -integral_max;
}
float i_term = pid->Ki * pid->integral;
// 微分项
pid->derivative = (pid->error - pid->last_error) / pid->sample_time;
float d_term = pid->Kd * pid->derivative;
// PID输出
pid->output = p_term + i_term + d_term;
// 输出限幅
if (pid->output > pid->output_max) {
pid->output = pid->output_max;
} else if (pid->output < pid->output_min) {
pid->output = pid->output_min;
}
pid->last_error = pid->error;
return pid->output;
}
2.3 条件积分法¶
只在误差较小时进行积分:
// 条件积分法
float PID_Compute_ConditionalIntegral(PID_Controller *pid,
float setpoint, float measured) {
pid->setpoint = setpoint;
pid->measured = measured;
pid->error = pid->setpoint - pid->measured;
// 比例项
float p_term = pid->Kp * pid->error;
// 条件积分:只在误差较小时积分
float error_threshold = 10.0f; // 误差阈值
if (fabs(pid->error) < error_threshold) {
pid->integral += pid->error * pid->sample_time;
}
float i_term = pid->Ki * pid->integral;
// 微分项
pid->derivative = (pid->error - pid->last_error) / pid->sample_time;
float d_term = pid->Kd * pid->derivative;
// PID输出
pid->output = p_term + i_term + d_term;
// 输出限幅
if (pid->output > pid->output_max) {
pid->output = pid->output_max;
} else if (pid->output < pid->output_min) {
pid->output = pid->output_min;
}
pid->last_error = pid->error;
return pid->output;
}
2.4 反向计算法(推荐)¶
当输出饱和时,反向计算积分项:
// 反向计算抗饱和
float PID_Compute_BackCalculation(PID_Controller *pid,
float setpoint, float measured) {
pid->setpoint = setpoint;
pid->measured = measured;
pid->error = pid->setpoint - pid->measured;
// 比例项
float p_term = pid->Kp * pid->error;
// 积分项
pid->integral += pid->error * pid->sample_time;
float i_term = pid->Ki * pid->integral;
// 微分项
pid->derivative = (pid->error - pid->last_error) / pid->sample_time;
float d_term = pid->Kd * pid->derivative;
// 计算未限幅的输出
float output_unsat = p_term + i_term + d_term;
// 输出限幅
pid->output = output_unsat;
if (pid->output > pid->output_max) {
pid->output = pid->output_max;
} else if (pid->output < pid->output_min) {
pid->output = pid->output_min;
}
// 反向计算积分项(抗饱和)
if (output_unsat != pid->output) {
// 输出饱和,调整积分项
float Kb = 1.0f / pid->Ki; // 反向计算系数
pid->integral += Kb * (pid->output - output_unsat) * pid->sample_time;
}
pid->last_error = pid->error;
return pid->output;
}
步骤3:微分滤波¶
3.1 微分噪声问题¶
微分项对测量噪声非常敏感,会导致: - 控制输出剧烈波动 - 执行器频繁动作 - 系统不稳定
解决方法:对微分项进行低通滤波
3.2 一阶低通滤波器¶
// 带微分滤波的PID结构体
typedef struct {
float Kp, Ki, Kd;
float setpoint, measured;
float error, last_error;
float integral, derivative;
float output_min, output_max, output;
float sample_time;
// 微分滤波参数
float derivative_filtered; // 滤波后的微分值
float filter_coefficient; // 滤波系数 (0-1)
} PID_Controller_Filtered;
// 初始化带滤波的PID
void PID_Init_Filtered(PID_Controller_Filtered *pid,
float kp, float ki, float kd, float filter_coef) {
pid->Kp = kp;
pid->Ki = ki;
pid->Kd = kd;
pid->setpoint = 0.0f;
pid->measured = 0.0f;
pid->error = 0.0f;
pid->last_error = 0.0f;
pid->integral = 0.0f;
pid->derivative = 0.0f;
pid->derivative_filtered = 0.0f;
pid->output_min = 0.0f;
pid->output_max = 100.0f;
pid->output = 0.0f;
pid->sample_time = 0.01f;
pid->filter_coefficient = filter_coef; // 典型值:0.1-0.3
}
// 带微分滤波的PID计算
float PID_Compute_Filtered(PID_Controller_Filtered *pid,
float setpoint, float measured) {
pid->setpoint = setpoint;
pid->measured = measured;
pid->error = pid->setpoint - pid->measured;
// 比例项
float p_term = pid->Kp * pid->error;
// 积分项(带抗饱和)
pid->integral += pid->error * pid->sample_time;
float integral_max = (pid->output_max - pid->output_min) / pid->Ki;
if (pid->integral > integral_max) {
pid->integral = integral_max;
} else if (pid->integral < -integral_max) {
pid->integral = -integral_max;
}
float i_term = pid->Ki * pid->integral;
// 微分项(带低通滤波)
pid->derivative = (pid->error - pid->last_error) / pid->sample_time;
// 一阶低通滤波
// filtered = α × new + (1-α) × old
pid->derivative_filtered = pid->filter_coefficient * pid->derivative +
(1.0f - pid->filter_coefficient) * pid->derivative_filtered;
float d_term = pid->Kd * pid->derivative_filtered;
// PID输出
pid->output = p_term + i_term + d_term;
// 输出限幅
if (pid->output > pid->output_max) {
pid->output = pid->output_max;
} else if (pid->output < pid->output_min) {
pid->output = pid->output_min;
}
pid->last_error = pid->error;
return pid->output;
}
步骤4:增量式PID¶
4.1 位置式 vs 增量式¶
位置式PID: - 输出是绝对值 - 需要累积所有历史误差 - 适合需要绝对位置控制的场合
增量式PID: - 输出是增量值 - 只需要最近几次误差 - 适合需要增量控制的场合(如PWM调节)
4.2 增量式PID实现¶
// 增量式PID结构体
typedef struct {
float Kp, Ki, Kd;
float error; // 当前误差 e(k)
float last_error; // 上次误差 e(k-1)
float prev_error; // 上上次误差 e(k-2)
float output_min, output_max;
float sample_time;
} PID_Incremental;
// 初始化增量式PID
void PID_Incremental_Init(PID_Incremental *pid, float kp, float ki, float kd) {
pid->Kp = kp;
pid->Ki = ki;
pid->Kd = kd;
pid->error = 0.0f;
pid->last_error = 0.0f;
pid->prev_error = 0.0f;
pid->output_min = -100.0f;
pid->output_max = 100.0f;
pid->sample_time = 0.01f;
}
// 增量式PID计算
float PID_Incremental_Compute(PID_Incremental *pid,
float setpoint, float measured) {
// 计算当前误差
pid->error = setpoint - measured;
// 增量式PID公式
// Δu(k) = Kp[e(k)-e(k-1)] + Ki×e(k) + Kd[e(k)-2e(k-1)+e(k-2)]
float delta_output = pid->Kp * (pid->error - pid->last_error) +
pid->Ki * pid->error * pid->sample_time +
pid->Kd * (pid->error - 2*pid->last_error + pid->prev_error) / pid->sample_time;
// 限制增量范围
if (delta_output > pid->output_max) {
delta_output = pid->output_max;
} else if (delta_output < pid->output_min) {
delta_output = pid->output_min;
}
// 更新误差历史
pid->prev_error = pid->last_error;
pid->last_error = pid->error;
return delta_output; // 返回增量值
}
// 使用示例
void Example_Incremental_PID(void) {
PID_Incremental pid;
PID_Incremental_Init(&pid, 2.0f, 0.5f, 0.1f);
float setpoint = 1000.0f; // 目标速度
float measured = 0.0f; // 当前速度
float pwm_duty = 50.0f; // 当前PWM占空比
for (int i = 0; i < 100; i++) {
// 计算增量
float delta_pwm = PID_Incremental_Compute(&pid, setpoint, measured);
// 更新PWM占空比
pwm_duty += delta_pwm;
// 限制PWM范围
if (pwm_duty > 100.0f) pwm_duty = 100.0f;
if (pwm_duty < 0.0f) pwm_duty = 0.0f;
// 应用PWM
Set_Motor_PWM(pwm_duty);
// 读取速度反馈
measured = Read_Motor_Speed();
HAL_Delay(10);
}
}
步骤5:PID参数整定¶
5.1 参数整定方法概述¶
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 试凑法 | 简单直观 | 耗时,依赖经验 | 简单系统 |
| 临界比例法 | 系统化 | 可能不稳定 | 稳定系统 |
| 经验公式法 | 快速 | 精度一般 | 初步调试 |
| 自整定法 | 自动化 | 实现复杂 | 复杂系统 |
5.2 试凑法(推荐初学者)¶
步骤1:只调Kp
1. 设置 Ki=0, Kd=0
2. 从小到大增加Kp
3. 观察系统响应:
- Kp太小:响应慢,误差大
- Kp适中:响应快,有小幅振荡
- Kp太大:振荡剧烈,不稳定
4. 选择响应快但不振荡的Kp值
步骤2:增加Ki
步骤3:增加Kd
5.3 经验公式法¶
根据系统类型选择初始参数:
温度控制系统:
电机速度控制:
位置控制系统:
5.4 在线整定工具¶
实现一个简单的在线整定工具:
// 在线参数调整
void PID_Online_Tuning(void) {
PID_Controller pid;
PID_Init(&pid, 1.0f, 0.1f, 0.05f);
PID_SetOutputLimits(&pid, 0.0f, 100.0f);
float setpoint = 1000.0f;
float measured = 0.0f;
printf("PID Online Tuning Tool\n");
printf("Commands:\n");
printf(" p+/p- : Increase/Decrease Kp by 0.1\n");
printf(" i+/i- : Increase/Decrease Ki by 0.01\n");
printf(" d+/d- : Increase/Decrease Kd by 0.01\n");
printf(" s : Show current parameters\n");
printf(" r : Reset PID\n");
printf(" q : Quit\n\n");
while (1) {
// 读取串口命令
if (UART_Available()) {
char cmd[3];
UART_ReadString(cmd, 3);
if (strcmp(cmd, "p+") == 0) {
pid.Kp += 0.1f;
printf("Kp = %.2f\n", pid.Kp);
} else if (strcmp(cmd, "p-") == 0) {
pid.Kp -= 0.1f;
if (pid.Kp < 0) pid.Kp = 0;
printf("Kp = %.2f\n", pid.Kp);
} else if (strcmp(cmd, "i+") == 0) {
pid.Ki += 0.01f;
printf("Ki = %.3f\n", pid.Ki);
} else if (strcmp(cmd, "i-") == 0) {
pid.Ki -= 0.01f;
if (pid.Ki < 0) pid.Ki = 0;
printf("Ki = %.3f\n", pid.Ki);
} else if (strcmp(cmd, "d+") == 0) {
pid.Kd += 0.01f;
printf("Kd = %.3f\n", pid.Kd);
} else if (strcmp(cmd, "d-") == 0) {
pid.Kd -= 0.01f;
if (pid.Kd < 0) pid.Kd = 0;
printf("Kd = %.3f\n", pid.Kd);
} else if (strcmp(cmd, "s") == 0) {
printf("Current Parameters:\n");
printf(" Kp = %.2f\n", pid.Kp);
printf(" Ki = %.3f\n", pid.Ki);
printf(" Kd = %.3f\n", pid.Kd);
} else if (strcmp(cmd, "r") == 0) {
PID_Reset(&pid);
printf("PID Reset\n");
} else if (strcmp(cmd, "q") == 0) {
break;
}
}
// PID控制循环
measured = Read_Motor_Speed();
float output = PID_Compute(&pid, setpoint, measured);
Set_Motor_PWM(output);
// 定期输出状态
static uint32_t last_print = 0;
if (HAL_GetTick() - last_print > 500) {
printf("SP:%.0f, PV:%.0f, Err:%.0f, Out:%.1f\n",
setpoint, measured, pid.error, output);
last_print = HAL_GetTick();
}
HAL_Delay(10);
}
}
步骤6:电机速度控制实战¶
6.1 硬件连接¶
STM32/Arduino 电机驱动(L298N) 直流电机
PWM_A ---------> ENA
IN1 ----------> IN1
IN2 ----------> IN2
OUT1 ----------> 电机+
OUT2 ----------> 电机-
ENCODER_A <--------- 编码器A相
ENCODER_B <--------- 编码器B相
GND ----------> GND
VCC <---------- 电源+ (12V)
GND <---------- 电源-
6.2 编码器速度测量¶
// 编码器变量
volatile int32_t encoder_count = 0;
volatile int32_t last_encoder_count = 0;
float motor_speed_rpm = 0.0f;
// 编码器参数
#define ENCODER_PPR 360 // 编码器每转脉冲数
#define GEAR_RATIO 1.0f // 减速比
// 编码器中断回调
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if (GPIO_Pin == ENCODER_A_PIN) {
// 读取B相判断方向
if (HAL_GPIO_ReadPin(ENCODER_B_PORT, ENCODER_B_PIN) == GPIO_PIN_SET) {
encoder_count++; // 正转
} else {
encoder_count--; // 反转
}
}
}
// 计算速度(在定时器中断中调用,如10ms)
void Calculate_Motor_Speed(void) {
// 计算脉冲差
int32_t pulse_diff = encoder_count - last_encoder_count;
last_encoder_count = encoder_count;
// 计算RPM
// RPM = (pulse_diff / PPR) × (60 / sample_time) / gear_ratio
motor_speed_rpm = (float)pulse_diff / ENCODER_PPR *
(60.0f / 0.01f) / GEAR_RATIO;
}
// 读取电机速度
float Read_Motor_Speed(void) {
return motor_speed_rpm;
}
6.3 电机驱动函数¶
// 电机方向枚举
typedef enum {
MOTOR_FORWARD = 0,
MOTOR_BACKWARD = 1,
MOTOR_STOP = 2
} Motor_Direction;
// 设置电机速度和方向
void Set_Motor_Speed(float speed, Motor_Direction direction) {
// 限制速度范围 0-100
if (speed > 100.0f) speed = 100.0f;
if (speed < 0.0f) speed = 0.0f;
// 计算PWM占空比
uint16_t pwm_value = (uint16_t)(speed * PWM_PERIOD / 100.0f);
// 设置方向
switch (direction) {
case MOTOR_FORWARD:
HAL_GPIO_WritePin(IN1_PORT, IN1_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(IN2_PORT, IN2_PIN, GPIO_PIN_RESET);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pwm_value);
break;
case MOTOR_BACKWARD:
HAL_GPIO_WritePin(IN1_PORT, IN1_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(IN2_PORT, IN2_PIN, GPIO_PIN_SET);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pwm_value);
break;
case MOTOR_STOP:
HAL_GPIO_WritePin(IN1_PORT, IN1_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(IN2_PORT, IN2_PIN, GPIO_PIN_RESET);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 0);
break;
}
}
// 应用PID输出到电机
void Apply_PID_Output(float pid_output) {
if (pid_output > 0) {
Set_Motor_Speed(pid_output, MOTOR_FORWARD);
} else if (pid_output < 0) {
Set_Motor_Speed(-pid_output, MOTOR_BACKWARD);
} else {
Set_Motor_Speed(0, MOTOR_STOP);
}
}
6.4 完整的速度控制程序¶
// 全局变量
PID_Controller speed_pid;
float target_speed = 0.0f;
uint8_t pid_enabled = 0;
// 初始化速度控制系统
void Speed_Control_Init(void) {
// 初始化PID控制器
PID_Init(&speed_pid, 2.0f, 0.5f, 0.1f);
PID_SetOutputLimits(&speed_pid, -100.0f, 100.0f);
PID_SetSampleTime(&speed_pid, 0.01f); // 10ms
// 初始化PWM
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
// 初始化编码器中断
HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
printf("Speed Control System Initialized\n");
}
// 速度控制任务(在10ms定时器中断中调用)
void Speed_Control_Task(void) {
// 计算当前速度
Calculate_Motor_Speed();
if (pid_enabled) {
// 读取当前速度
float current_speed = Read_Motor_Speed();
// PID计算
float pid_output = PID_Compute(&speed_pid, target_speed, current_speed);
// 应用到电机
Apply_PID_Output(pid_output);
}
}
// 主程序
int main(void) {
// HAL初始化
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM1_Init();
MX_TIM2_Init();
MX_USART1_UART_Init();
// 初始化速度控制
Speed_Control_Init();
printf("\n=== Motor Speed Control with PID ===\n");
printf("Commands:\n");
printf(" s<speed> : Set target speed (e.g., s500)\n");
printf(" e : Enable PID control\n");
printf(" d : Disable PID control\n");
printf(" p<value> : Set Kp (e.g., p2.5)\n");
printf(" i<value> : Set Ki (e.g., i0.5)\n");
printf(" k<value> : Set Kd (e.g., k0.1)\n");
printf(" r : Reset PID\n");
printf(" ? : Show status\n\n");
// 启动10ms定时器中断
HAL_TIM_Base_Start_IT(&htim2);
while (1) {
// 处理串口命令
if (UART_Available()) {
char cmd = UART_Read();
switch (cmd) {
case 's': {
// 设置目标速度
float speed;
scanf("%f", &speed);
target_speed = speed;
printf("Target speed set to: %.0f RPM\n", target_speed);
break;
}
case 'e':
pid_enabled = 1;
printf("PID control enabled\n");
break;
case 'd':
pid_enabled = 0;
Set_Motor_Speed(0, MOTOR_STOP);
printf("PID control disabled\n");
break;
case 'p': {
float kp;
scanf("%f", &kp);
speed_pid.Kp = kp;
printf("Kp set to: %.2f\n", kp);
break;
}
case 'i': {
float ki;
scanf("%f", &ki);
speed_pid.Ki = ki;
printf("Ki set to: %.3f\n", ki);
break;
}
case 'k': {
float kd;
scanf("%f", &kd);
speed_pid.Kd = kd;
printf("Kd set to: %.3f\n", kd);
break;
}
case 'r':
PID_Reset(&speed_pid);
printf("PID reset\n");
break;
case '?': {
float current_speed = Read_Motor_Speed();
printf("\n--- Status ---\n");
printf("PID: %s\n", pid_enabled ? "Enabled" : "Disabled");
printf("Target: %.0f RPM\n", target_speed);
printf("Current: %.0f RPM\n", current_speed);
printf("Error: %.0f RPM\n", speed_pid.error);
printf("Output: %.1f%%\n", speed_pid.output);
printf("Kp=%.2f, Ki=%.3f, Kd=%.3f\n",
speed_pid.Kp, speed_pid.Ki, speed_pid.Kd);
printf("-------------\n\n");
break;
}
}
}
// 定期输出状态(每500ms)
static uint32_t last_print = 0;
if (HAL_GetTick() - last_print > 500 && pid_enabled) {
float current_speed = Read_Motor_Speed();
printf("Target:%.0f, Current:%.0f, Error:%.0f, Output:%.1f\n",
target_speed, current_speed, speed_pid.error, speed_pid.output);
last_print = HAL_GetTick();
}
HAL_Delay(10);
}
}
// 定时器中断回调(10ms)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM2) {
Speed_Control_Task();
}
}
步骤7:Arduino实现¶
7.1 Arduino完整代码¶
// Arduino PID电机速度控制
// 引脚定义
const int MOTOR_PWM = 9;
const int MOTOR_IN1 = 7;
const int MOTOR_IN2 = 8;
const int ENCODER_A = 2; // 中断引脚
const int ENCODER_B = 3; // 中断引脚
// 编码器变量
volatile long encoder_count = 0;
long last_encoder_count = 0;
float motor_speed_rpm = 0.0;
// PID参数
float Kp = 2.0;
float Ki = 0.5;
float Kd = 0.1;
// PID变量
float setpoint = 0.0;
float measured = 0.0;
float error = 0.0;
float last_error = 0.0;
float integral = 0.0;
float derivative = 0.0;
float output = 0.0;
// 时间变量
unsigned long last_time = 0;
float sample_time = 0.01; // 10ms
// 编码器参数
const int ENCODER_PPR = 360;
void setup() {
Serial.begin(115200);
// 配置引脚
pinMode(MOTOR_PWM, OUTPUT);
pinMode(MOTOR_IN1, OUTPUT);
pinMode(MOTOR_IN2, OUTPUT);
pinMode(ENCODER_A, INPUT_PULLUP);
pinMode(ENCODER_B, INPUT_PULLUP);
// 配置编码器中断
attachInterrupt(digitalPinToInterrupt(ENCODER_A), encoderISR, RISING);
// 配置PWM频率(31kHz)
TCCR1B = TCCR1B & 0b11111000 | 0x01;
Serial.println("Motor Speed Control with PID");
Serial.println("Commands:");
Serial.println(" s<speed> : Set target speed");
Serial.println(" p<value> : Set Kp");
Serial.println(" i<value> : Set Ki");
Serial.println(" d<value> : Set Kd");
Serial.println(" ? : Show status");
}
void loop() {
// 处理串口命令
if (Serial.available()) {
char cmd = Serial.read();
if (cmd == 's') {
setpoint = Serial.parseFloat();
Serial.print("Target speed: ");
Serial.println(setpoint);
} else if (cmd == 'p') {
Kp = Serial.parseFloat();
Serial.print("Kp = ");
Serial.println(Kp);
} else if (cmd == 'i') {
Ki = Serial.parseFloat();
Serial.print("Ki = ");
Serial.println(Ki);
} else if (cmd == 'd') {
Kd = Serial.parseFloat();
Serial.print("Kd = ");
Serial.println(Kd);
} else if (cmd == '?') {
Serial.println("--- Status ---");
Serial.print("Target: ");
Serial.println(setpoint);
Serial.print("Current: ");
Serial.println(measured);
Serial.print("Error: ");
Serial.println(error);
Serial.print("Output: ");
Serial.println(output);
Serial.print("Kp=");
Serial.print(Kp);
Serial.print(", Ki=");
Serial.print(Ki);
Serial.print(", Kd=");
Serial.println(Kd);
}
}
// PID控制循环(10ms)
unsigned long current_time = millis();
if (current_time - last_time >= 10) {
last_time = current_time;
// 计算速度
calculateSpeed();
// PID计算
pidCompute();
// 应用输出
applyMotorOutput(output);
// 定期输出(每500ms)
static unsigned long last_print = 0;
if (current_time - last_print >= 500) {
Serial.print("SP:");
Serial.print(setpoint);
Serial.print(", PV:");
Serial.print(measured);
Serial.print(", Err:");
Serial.print(error);
Serial.print(", Out:");
Serial.println(output);
last_print = current_time;
}
}
}
// 编码器中断服务函数
void encoderISR() {
if (digitalRead(ENCODER_B) == HIGH) {
encoder_count++;
} else {
encoder_count--;
}
}
// 计算速度
void calculateSpeed() {
long pulse_diff = encoder_count - last_encoder_count;
last_encoder_count = encoder_count;
// 计算RPM
motor_speed_rpm = (float)pulse_diff / ENCODER_PPR * (60.0 / sample_time);
measured = motor_speed_rpm;
}
// PID计算
void pidCompute() {
// 计算误差
error = setpoint - measured;
// 比例项
float p_term = Kp * error;
// 积分项(带抗饱和)
integral += error * sample_time;
float integral_max = 100.0 / Ki;
if (integral > integral_max) integral = integral_max;
if (integral < -integral_max) integral = -integral_max;
float i_term = Ki * integral;
// 微分项
derivative = (error - last_error) / sample_time;
float d_term = Kd * derivative;
// PID输出
output = p_term + i_term + d_term;
// 输出限幅
if (output > 100.0) output = 100.0;
if (output < -100.0) output = -100.0;
// 保存误差
last_error = error;
}
// 应用电机输出
void applyMotorOutput(float pwm_value) {
if (pwm_value > 0) {
// 正转
digitalWrite(MOTOR_IN1, HIGH);
digitalWrite(MOTOR_IN2, LOW);
analogWrite(MOTOR_PWM, (int)(pwm_value * 2.55));
} else if (pwm_value < 0) {
// 反转
digitalWrite(MOTOR_IN1, LOW);
digitalWrite(MOTOR_IN2, HIGH);
analogWrite(MOTOR_PWM, (int)(-pwm_value * 2.55));
} else {
// 停止
digitalWrite(MOTOR_IN1, LOW);
digitalWrite(MOTOR_IN2, LOW);
analogWrite(MOTOR_PWM, 0);
}
}
调试技巧¶
1. 数据可视化¶
使用串口绘图工具(如Arduino Serial Plotter)实时观察PID效果:
// 输出格式化数据用于绘图
void Print_For_Plotter(void) {
printf("Setpoint:%d,Measured:%d,Output:%d\n",
(int)target_speed,
(int)Read_Motor_Speed(),
(int)speed_pid.output);
}
2. 常见问题排查¶
问题1:电机不转 - 检查电源电压是否足够 - 检查PWM信号是否正常 - 检查电机驱动器连接 - 检查PID输出是否为0
问题2:速度振荡 - Kp过大,减小Kp - Ki过大,减小Ki - 增加微分项Kd - 检查编码器信号质量
问题3:响应慢 - Kp过小,增大Kp - Ki过小,增大Ki - 检查采样周期是否合适 - 检查电机负载是否过大
问题4:超调严重 - Ki过大,减小Ki - 增加Kd - 检查抗积分饱和是否生效 - 使用软启动
问题5:稳态误差大 - Ki过小,增大Ki - 检查积分项是否工作 - 检查输出是否饱和 - 检查速度测量精度
3. 性能评估指标¶
// 性能评估
typedef struct {
float rise_time; // 上升时间(10%-90%)
float settling_time; // 稳定时间(进入±5%范围)
float overshoot; // 超调量(%)
float steady_error; // 稳态误差
} PID_Performance;
void Evaluate_PID_Performance(PID_Performance *perf) {
// 记录响应曲线
// 计算性能指标
// 输出评估结果
}
进阶应用¶
1. 串级PID控制¶
用于位置控制系统,外环控制位置,内环控制速度:
// 串级PID结构
typedef struct {
PID_Controller position_pid; // 外环:位置PID
PID_Controller speed_pid; // 内环:速度PID
} Cascade_PID;
// 串级PID计算
float Cascade_PID_Compute(Cascade_PID *cascade,
float target_position,
float current_position,
float current_speed) {
// 外环:位置PID,输出为目标速度
float target_speed = PID_Compute(&cascade->position_pid,
target_position,
current_position);
// 内环:速度PID,输出为PWM
float pwm_output = PID_Compute(&cascade->speed_pid,
target_speed,
current_speed);
return pwm_output;
}
2. 前馈控制¶
结合前馈和反馈控制,提高响应速度:
// 前馈+PID控制
float Feedforward_PID_Compute(PID_Controller *pid,
float setpoint,
float measured,
float feedforward_gain) {
// 前馈项
float feedforward = feedforward_gain * setpoint;
// 反馈项(PID)
float feedback = PID_Compute(pid, setpoint, measured);
// 总输出
float output = feedforward + feedback;
// 限幅
if (output > pid->output_max) output = pid->output_max;
if (output < pid->output_min) output = pid->output_min;
return output;
}
3. 自适应PID¶
根据系统状态自动调整PID参数:
// 自适应PID
void Adaptive_PID_Compute(PID_Controller *pid,
float setpoint,
float measured) {
float error = setpoint - measured;
float error_abs = fabs(error);
// 根据误差大小调整参数
if (error_abs > 100.0f) {
// 误差大:增大Kp,减小Ki和Kd
pid->Kp = 3.0f;
pid->Ki = 0.2f;
pid->Kd = 0.05f;
} else if (error_abs > 20.0f) {
// 误差中等:正常参数
pid->Kp = 2.0f;
pid->Ki = 0.5f;
pid->Kd = 0.1f;
} else {
// 误差小:减小Kp,增大Ki
pid->Kp = 1.0f;
pid->Ki = 0.8f;
pid->Kd = 0.15f;
}
// 执行PID计算
PID_Compute(pid, setpoint, measured);
}
实战练习¶
练习1:温度控制¶
使用PID控制加热器,维持恒定温度:
任务: 1. 读取温度传感器(如DS18B20) 2. 使用PID控制PWM输出 3. 驱动加热器(通过MOSFET) 4. 目标:将温度稳定在设定值±1°C
提示: - 温度系统响应慢,采样周期可设为1秒 - Kp建议范围:5-20 - Ki建议范围:0.1-1.0 - Kd建议范围:1-10
练习2:平衡小车¶
使用PID控制平衡小车姿态:
任务: 1. 读取MPU6050姿态角 2. 使用PID计算电机输出 3. 控制两个电机保持平衡 4. 目标:小车能够自主平衡
提示: - 需要快速响应,采样周期10ms - 可能需要串级PID(角度+角速度) - 参数整定需要耐心
练习3:位置跟踪¶
使用编码器实现精确位置控制:
任务: 1. 读取编码器位置 2. 使用串级PID(位置+速度) 3. 实现精确定位 4. 目标:定位精度±5个脉冲
提示: - 外环(位置):Kp=0.5-2.0, Ki=0, Kd=0 - 内环(速度):Kp=1.0-3.0, Ki=0.3-1.0, Kd=0.05-0.2
总结¶
关键要点¶
- PID三个环节:
- P(比例):快速响应,但有稳态误差
- I(积分):消除稳态误差,但可能超调
-
D(微分):减少超调,但对噪声敏感
-
数字实现要点:
- 使用离散化公式
- 注意采样周期的影响
- 实现抗积分饱和
-
对微分项进行滤波
-
参数整定原则:
- 先P后I再D
- 从小到大逐步调整
- 观察系统响应曲线
-
根据实际效果微调
-
常见问题:
- 积分饱和:使用抗饱和措施
- 微分噪声:使用低通滤波
- 参数不当:系统化整定
- 采样周期:根据系统特性选择
学习检查清单¶
完成本教程后,你应该能够:
- 理解PID控制器的工作原理
- 实现基础的位置式PID
- 实现增量式PID
- 处理积分饱和问题
- 对微分项进行滤波
- 整定PID参数
- 应用PID控制电机速度
- 调试和优化PID性能
下一步学习¶
- 高级控制算法:模糊PID、神经网络PID
- 状态空间控制:现代控制理论
- 模型预测控制:MPC算法
- 自适应控制:参数自整定
- 鲁棒控制:H∞控制、滑模控制
参考资源¶
书籍: - 《自动控制原理》- 胡寿松 - 《PID控制器参数整定方法》 - 《嵌入式系统控制算法》
在线资源: - PID控制器仿真工具 - Arduino PID库文档 - STM32 PID应用笔记
开源项目: - Arduino PID Library - STM32 Motor Control SDK - ROS PID Controller
附录¶
A. 完整代码仓库¶
完整的示例代码可在以下仓库获取: - GitHub: [embedded-pid-control-examples] - 包含STM32和Arduino完整工程 - 包含测试数据和调试工具
B. PID参数速查表¶
| 应用场景 | Kp | Ki | Kd | 采样周期 |
|---|---|---|---|---|
| 电机速度控制 | 1-5 | 0.1-1.0 | 0.01-0.5 | 10ms |
| 温度控制 | 5-20 | 0.1-1.0 | 1-10 | 1s |
| 位置控制 | 1-10 | 0.01-0.5 | 0.1-2.0 | 10ms |
| 压力控制 | 2-10 | 0.1-1.0 | 0.1-1.0 | 100ms |
C. 故障排查流程图¶
电机不转?
├─ 检查电源
├─ 检查PWM信号
├─ 检查驱动器
└─ 检查PID输出
速度振荡?
├─ 减小Kp
├─ 减小Ki
├─ 增大Kd
└─ 检查编码器
响应慢?
├─ 增大Kp
├─ 增大Ki
└─ 检查负载
稳态误差大?
├─ 增大Ki
├─ 检查积分项
└─ 检查输出饱和
作者: 嵌入式知识平台
最后更新: 2024-01-15
版本: 1.0
如有问题或建议,欢迎反馈!