STM32 实现PID

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

头文件 pid_controller.h
cpp 复制代码
#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
cpp 复制代码
#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. 积分项更新(带限幅抗饱和)[2,3](@ref)
    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. 微分项优化:用测量值微分而非误差微分[6](@ref)
    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;
}

⚙️ 二、STM32F103硬件集成示例

场景:直流电机速度控制(定时器中断触发计算)
cpp 复制代码
#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. ​微分平滑化 ​

    使用测量值微分而非误差微分,减少设定值突变造成的冲击:

      cpp 复制代码
      float derivative = (measurement - prev_measurement) / dt; // 替代 error - prev_error
  3. ​动态参数调整 ​

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

    cpp 复制代码
    void PID_Tune(PIDController* pid, float Kp, float Ki, float Kd) {
        pid->Kp = Kp;
        pid->Ki = Ki;
        pid->Kd = Kd;
        // pid->integral = 0; // 可选:重置积分防突变
    }
  4. ​资源占用优化​

    • 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)

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

    cpp 复制代码
    // 内环(角速度):高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. ​磁悬浮装置​(霍尔传感器反馈)

    cpp 复制代码
    PID_Init(&levitation_pid, 4.0f, 1.0f, 30.0f, -500, 500, 200.0f);

​调试工具建议 ​:

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

复制代码
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℃)等场景验证

相关推荐
mftang1 小时前
CAN总线控制段:位级结构、DLC编码、代际扩展与错误处理的系统性分析
单片机·嵌入式硬件·can总线·控制段·数据长度代码
yi0118 小时前
LeetCode 219:存在重复元素 II——哈希表记录“最近一次出现的位置”
数据结构·人工智能·笔记·python·算法·leetcode·哈希表
BSD_CGQ10 小时前
FSR传感器STM32 DMA高速采样工程实现(高精度、低CPU占用)
stm32·单片机·嵌入式硬件·压力传感器·源头工厂·柔性薄膜压力传感器·可变电阻压力传感器
灯澜忆梦11 小时前
【面向对象编程C++】| 基础语法
java·c++·算法
小蒋观天下11 小时前
专项方案:大场景港口AI安防、多干扰环境下的算法调优与落地实操
人工智能·深度学习·算法·安全·机器学习·计算机视觉·ai大模型
Evand J11 小时前
【电机滤波例程5】渐消因子自适应扩展卡尔曼滤波(EKF),MATLAB。PMSM负载突变下的状态估计。附下载链接
算法·matlab
H.莓飛11 小时前
【数据结构】二叉树_OJ题
linux·开发语言·数据结构·算法
好想像大佬一样能够ak所有12 小时前
Leetcode35.搜索插入位置
数据结构·算法·leetcode
aqiu11111112 小时前
【C++算法打怪专栏】LeetCode 165. 比较版本号(双指针与字符串流法)
c++·算法·leetcode
黄金龙PLUS12 小时前
如何基于置换算法来设计对称加密算法
算法·网络安全·密码学·哈希算法·同态加密