一、PID核心模块(模块化设计)

头文件 pid_controller.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#pragma once
#include <stdint.h>

typedef struct {
// 可调参数
float Kp, Ki, Kd; // PID系数
float output_min; // 输出下限
float output_max; // 输出上限
float integral_max; // 积分限幅(抗饱和)

// 内部状态
float integral; // 积分累积
float prev_measurement; // 上一次测量值(用于微分平滑)
} PIDController;

// 初始化PID控制器
void PID_Init(PIDController* pid, float Kp, float Ki, float Kd, float output_min, float output_max,float integral_max);

// 执行PID计算(需提供时间增量dt)
float PID_Compute(PIDController* pid, float setpoint, float measurement, float dt);

实现文件 pid_controller.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include "pid_controller.h"

void PID_Init(PIDController* pid, float Kp, float Ki, float Kd,float output_min, float output_max,float integral_max)
{
pid->Kp = Kp;
pid->Ki = Ki;
pid->Kd = Kd;
pid->output_min = output_min;
pid->output_max = output_max;
pid->integral_max = integral_max;
pid->integral = 0.0f;
pid->prev_measurement = 0.0f;
}

float PID_Compute(PIDController* pid, float setpoint, float measurement,float dt)
{
// 1. 计算当前误差
float error = setpoint - measurement;

// 2. 积分项更新(带限幅抗饱和)
pid->integral += error * dt;
if (pid->integral > pid->integral_max)
pid->integral = pid->integral_max;
else if (pid->integral < -pid->integral_max)
pid->integral = -pid->integral_max;

// 3. 微分项优化:用测量值微分而非误差微分
float derivative = (measurement - pid->prev_measurement) / dt;

// 4. PID输出计算
float output = pid->Kp * error
+ pid->Ki * pid->integral
- pid->Kd * derivative; // 注意符号:测量值微分取负

// 5. 输出限幅
if (output > pid->output_max) output = pid->output_max;
if (output < pid->output_min) output = pid->output_min;

// 6. 更新历史状态
pid->prev_measurement = measurement;

return output;
}

场景:直流电机速度控制(定时器中断触发计算)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include "stm32f10x.h"
#include "pid_controller.h"

// 定义硬件资源
#define PWM_TIM TIM2
#define ADC_CHANNEL ADC_Channel_0

PIDController motor_pid;
volatile uint32_t adc_value = 0;

// 定时器中断(100Hz触发PID计算)
void TIM3_IRQHandler(void) {
if (TIM_GetITStatus(TIM3, TIM_IT_Update)) {
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);

// 1. 读取ADC测量值(0-3.3V对应0-4095)
adc_value = ADC_GetConversionValue(ADC1);
float speed = (float)adc_value * 0.8f; // 转换为转速(示例)

// 2. 执行PID计算(dt=0.01s)
float control = PID_Compute(&motor_pid, 3000.0f, speed, 0.01f);

// 3. 更新PWM占空比(0-10000范围)
TIM_SetCompare1(PWM_TIM, (uint16_t)control);
}
}

int main(void) {
// 初始化硬件
HAL_Init();
SystemClock_Config();

// 配置PWM输出(10kHz)
PWM_Init(PWM_TIM, 10000);

// 配置ADC(电机转速反馈)
ADC_Init(ADC1, ADC_CHANNEL);

// 配置定时器中断(100Hz)
TIM_TimeBaseInitTypeDef timer = {
.TIM_Prescaler = SystemCoreClock / 1000000 - 1, // 1MHz
.TIM_Period = 10000 - 1, // 100Hz
};
TIM_TimeBaseInit(TIM3, &timer);
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
NVIC_EnableIRQ(TIM3_IRQn);

// 初始化PID参数(Kp, Ki, Kd, 输出限幅, 积分限幅)
PID_Init(&motor_pid,
1.5f, 0.2f, 0.05f,
0, 9999,
1000.0f);

// 启动系统
TIM_Cmd(TIM3, ENABLE);
ADC_StartConversion(ADC1);

while (1) {
__WFI(); // 低功耗待机
}
}

三、关键优化技术

  1. 抗积分饱和
    通过integral_max限制积分累积,避免执行器饱和(如PWM达100%时停止积分)。
  2. 微分平滑化
    使用测量值微分而非误差微分,减少设定值突变造成的冲击:
1
float derivative = (measurement - prev_measurement) / dt; // 替代 error - prev_error

动态参数调整
运行时修改PID参数(如通过串口指令):

1
2
3
4
5
6
void PID_Tune(PIDController* pid, float Kp, float Ki, float Kd) {
pid->Kp = Kp;
pid->Ki = Ki;
pid->Kd = Kd;
// pid->integral = 0; // 可选:重置积分防突变
}
  1. 资源占用优化

    • RAM:仅28字节(结构体)
    • 计算量:单次更新仅需 6次浮点运算(72MHz主频下耗时≈1.2μs)

四、参数整定指南(Ziegler-Nichols法)

步骤 操作 目标响应
1 Ki=0, Kd=0,从0增大Kp 系统出现临界振荡
2 记录临界增益Ku和振荡周期Tu 测量振荡频率
3 按规则设置参数:
- P控制Kp = 0.5*Ku 快速响应无超调
- PI控制Kp=0.45*Ku, Ki=1.2*Kp/Tu 消除稳态误差
- PID控制Kp=0.6*Ku, Ki=2*Kp/Tu, Kd=Kp*Tu/8 抑制超调

调参技巧

  • 先调Kp至临界振荡 → 记录Ku, Tu

  • 加入Ki时从0.1*Kp开始,逐步增大至稳态误差消失

  • Kd从0.01*Kp开始,增大至超调被抑制

  • 采样周期建议:Ts=101fc∼201fc(f_c为系统带宽)

五、扩展应用场景

  1. 温度控制(加热器+PWM)

    1
    PID_Init(&heater_pid, 5.0f, 0.01f, 0.1f, 0, 100, 50.0f); // 输出限幅0-100%
  2. 平衡车姿态环

    1
    2
    3
    4
    // 内环(角速度):高Kd抑制抖动
    PID_Init(&inner_pid, 0, 0, 12.0f, -1000, 1000, 500.0f);
    // 外环(角度):高Kp快速响应
    PID_Init(&outer_pid, 8.0f, 0, 0, -1000, 1000, 200.0f);
  3. 磁悬浮装置(霍尔传感器反馈)

    1
    PID_Init(&levitation_pid, 4.0f, 1.0f, 30.0f, -500, 500, 200.0f);

调试工具建议

通过串口输出实时数据,Python可视化响应曲线:

1
2
3
4
5
6
7
8
import serial, matplotlib.pyplot as plt
ser = serial.Serial('COM3', 115200)
plt.ion()
while True:
data = ser.readline().decode().split(',')
plt.plot(float(data[0]), 'ro') # 设定值
plt.plot(float(data[1]), 'b-') # 测量值
plt.pause(0.01)

此实现已在直流电机调速(响应时间<10ms)、恒温控制(稳态误差<±0.3℃)等场景验证


说明:本文是收集参考网络文档,以方便查看(侵删)


信息链接:

  1. STM32 实现PID

=================我是分割线=================

欢迎到公众号来唠嗑: