PID算法原理详解及其实践指南

文章目录

  • PID算法详解与实现
  • 一、什么是PID算法
  • 二、PID算法的C语言实现
  • 三、使用cJSON辅助调节PID
  • 四、使用VOFA+调节PID
  • PID算法详解与实现

    一、什么是PID算法

    1. PID算法简介

    2. 📘 定义:PID控制(比例-积分-微分控制)是一种常见的反馈控制算法,用于将实际输出与目标值进行比较,并根据偏差进行调整。🌐[维基百科]
    3. 🔧 应用:PID控制广泛应用于工业自动化系统,如温度控制、位置控制、速度控制等。🌐[知乎]
    4. PID控制器的基本原理

    5. ⚙️ 比例控制(P):比例控制根据当前偏差值进行调整,调节器的输出与偏差成正比。控制力度过大可能导致系统不稳定,过小则响应过慢。
    6. 🧮 积分控制(I):积分控制根据历史误差累积进行调整,能消除长期存在的偏差,但可能导致系统超调或振荡。
    7. 📈 微分控制(D):微分控制根据偏差变化速率进行调整,能预测误差趋势,快速响应变化,但对噪声较敏感。🌐[博客网]
    8. PID控制器的优缺点

    9. 优点
    10. 结构简单,易于实现。
    11. 控制效果好,适应性强。
    12. 缺点
    13. 参数整定复杂,需要经验和调试。
    14. 对环境变化敏感,可能需要定期调整。🌐[CSDN]

    二、PID算法的C语言实现

    1. PID算法的数学公式

    2. 📐 标准公式
      PID公式
    3. 其中,( K_p ) 是比例系数,( K_i ) 是积分系数,( K_d ) 是微分系数,( e(t) ) 是当前误差。

    4. PID算法的伪代码

    5. 💻 伪代码
    6. previous_error = 0        // 初始化上一次误差为0
      integral = 0              // 初始化积分项为0
      loop:                     // 开始循环
      error = setpoint - measured_value    // 计算当前误差
      integral = integral + error * dt     // 计算误差的累积积分
      derivative = (error - previous_error) / dt  // 计算误差的变化率(微分)
      output = Kp * error + Ki * integral + Kd * derivative  // 计算PID输出
      previous_error = error    // 更新上一次误差为当前误差
      wait(dt)                 // 等待时间间隔dt
      goto loop                // 回到循环开始
      
    7. PID算法的C语言实现代码

    8. 💾 实现代码

    9. PID.h

    10. #ifndef __PID_H
      #define __PID_H
      
      typedef struct
      {
          float target_val; // 目标值
          float actual_val; // 实际值
          float err;        // 误差
          float err_last;   // 上次误差
          float err_sum;    // 累计误差
          float Kp;         // 比例
          float Ki;         // 积分
          float Kd;         // 微分系数
      } tPID;
      
      // 定义一个结构体类型变量
      extern tPID PID;       //结构体名根据自己需要设置
      
      // 给结构体类型变量赋初值
      void PID_Init(void);
      // PID控制函数
      float PID_Realize(tPID *pid, float actual_val);
      float Incremental_PID_Realize(tPID *PID, float position, float actual_val);
      
      #endif
      
      
    11. PID.c
    12. #include "PID.h"
      
      // 定义一个结构体类型变量
      tPID PID;
      
      // 给结构体类型变量赋初值
      void PID_Init()
      {
          PID.target_val = 0.00;
          PID.actual_val = 0.00;
          PID.err = 0.00;
          PID.err_last = 0.00;
          PID.err_sum = 0.00;
          PID.Kp = 0.00;
          PID.Ki = 0.00;
          PID.Kd = 0.00;
      }
      
      /*******************
      *  @brief  位置式PID控制函数
      *  @param  *PID: 指向PID控制器结构体的指针
      *          actual_val: 当前实际值
      *  @return 调节后的输出值
      *******************/
      float Position_PID_Realize(tPID *PID, float actual_val)
      {
          PID->actual_val = actual_val;                 // 传递真实值
          PID->err = PID->target_val - PID->actual_val; // 当前误差=目标值-真实值
          PID->err_sum += PID->err;                     // 误差累计值 = 当前误差累计和
          // 使用PID控制 输出 = Kp*当前误差  +  Ki*误差累计值 + Kd*(当前误差-上次误差)
          PID->actual_val = PID->Kp * PID->err + PID->Ki * PID->err_sum + PID->Kd * (PID->err - PID->err_last);
          PID->err_last = PID->err; // 保存当前误差以备下次使用
          return PID->actual_val;   // 返回控制器输出的实际值
      }
      
      /*******************
       *  @brief  增量式PID控制函数
       *  @param  *PID: 指向PID控制器结构体的指针
       *          actual_val: 当前实际值
       *          target_val: 目标值
       *  @return 调节后的输出位置
       *******************/
      float Incremental_PID_Realize(tPID *PID, float actual_val, float target_val)
      {
          float increment;          // 增量变量
          PID->err = actual_val - target_val; // 当前误差
          PID->err_sum += PID->err; // 误差累计值 = 当前误差累计和
          // 计算增量 = Kp*当前误差  +  Ki*误差累计值 + Kd*(当前误差-上次误差)
          increment = PID->Kp * PID->err + PID->Ki * PID->err_sum + PID->Kd * (PID->err - PID->err_last);
          PID->err_last = PID->err;    // 保存当前误差以备下次使用
          return actual_val + increment; // 返回当前位置加上增量
      }
      
      
    13. 代码注释与解析

    14. 结构体定义:PID 结构体包含了PID参数和历史误差信息。
    15. 初始化函数:PID_Init函数初始化了PID控制器的参数。

    三、使用cJSON辅助调节PID

    1. cJSON简介

       cJSON是一个轻量级的JSON解析库,用于在C语言中处理JSON数据。它的主要功能包括生成和解析JSON字符串,使得在嵌入式系统中处理数据更加方便快捷

    2. cJSON的主要作用

    3. 📦 数据打包: 将PID算法的调试数据打包成JSON格式,以便传输。
    4. 🔍 数据解析: 在VOFA+中解析JSON数据并绘制波形。
    5. cJSON的使用

    6. 安装与配置:
    7. 首先下载并安装cJSON库,将其添加到项目中。可以通过以下方式安装:🌐[github链接]
    8. cJSON.ccJSON.h文件添加到工程中,并在代码中包含头文件。
    9. JSON数据的解析与调试:
    10. #include "cJSON.h"
      #include <string.h>
      
      cJSON *cJsonData ,*cJsonVlaue;
      
      if (Usart_WaitReasFinish() == 0) // 是否接收完毕
      {
          cJsonData = cJSON_Parse((const char *)Usart1_ReadBuf); // 解析接收缓冲区中的JSON数据
      
          if (cJSON_GetObjectItem(cJsonData, "p") != NULL) // 检查是否存在键"p"
          {
              cJsonVlaue = cJSON_GetObjectItem(cJsonData, "p"); // 获取键"p"的值
              p = cJsonVlaue->valuedouble;                      // 将值赋给变量p
              PID.Kp = p;                                       // 设置PID的Kp参数
          }
          if (cJSON_GetObjectItem(cJsonData, "i") != NULL) // 检查是否存在键"i"
          {
              cJsonVlaue = cJSON_GetObjectItem(cJsonData, "i"); // 获取键"i"的值
              i = cJsonVlaue->valuedouble;                      // 将值赋给变量i
              PID.Ki = i;                                       // 设置PID的Ki参数
          }
          if (cJSON_GetObjectItem(cJsonData, "d") != NULL) // 检查是否存在键"d"
          {
              cJsonVlaue = cJSON_GetObjectItem(cJsonData, "d"); // 获取键"d"的值
              d = cJsonVlaue->valuedouble;                      // 将值赋给变量d
              PID.Kd = d;                                       // 设置PID的Kd参数
          }
          if (cJSON_GetObjectItem(cJsonData, "target_val") != NULL) // 检查是否存在键"target_val1"
          {
              cJsonVlaue = cJSON_GetObjectItem(cJsonData, "target_val"); // 获取键"target_val"的值
              target_val1 = cJsonVlaue->valuedouble;                      // 将值赋给变量target_val
              PID.target_val = target_val;                               // 设置PID的目标值
          }
          if (cJsonData != NULL)
          {
              cJSON_Delete(cJsonData); // 释放cJSON对象内存
          }
          memset(Usart1_ReadBuf, 0, 255); // 清空接收缓冲区
      }
      printf("P:%.3f  I:%.3f  D:%.3f target_val:%.3f\n", p, i, d, target_val); // 打印当前PID参数和目标值
      

    四、使用VOFA+调节PID

    1. VOFA+的简介
      VOFA+是一款直观、灵活、强大的插件驱动高自由度的上位机,在与电气打交道的领域里,如自动化、嵌入式、物联网、机器人等,都能看到VOFA+的身影。VOFA+的名字来源于:Volt/伏特、Ohm/欧姆、Fala/法拉、Ampere/安培,是电气领域的基础单位,与他们的发明者——4位电子物理学领域的科学巨人,分别同名。他们的首字母共同构成了VOFA+的名字。

    2. VOFA+的使用

    3. 🛠 基础设置:
      基础设置

    4. 🖥 界面设置:
      界面设置

    5. 实时调参

    6. ⚙️ 配置串口(以串口1为例)

    7. 调大堆栈
    8. 调大堆栈

    9. 软件开启中断
      软件开启中断

    10. 开启接收中断

    11. __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);	//开启串口1接收中断
      
    12. 中断回调函数
    13. uint8_t Usart1_ReadBuf[256];	//串口1 缓冲数组
      uint8_t Usart1_ReadCount = 0;	//串口1 接收字节计数
      if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE))//判断huart1 是否读到字节
      {
              if(Usart1_ReadCount >= 255) Usart1_ReadCount = 0;
              HAL_UART_Receive(&huart1,&Usart1_ReadBuf[Usart1_ReadCount++],1,1000);
      }
      
    14. 编写函数用于判断串口是否发送完一帧数据
    15. extern uint8_t Usart1_ReadBuf[255];	//串口1 缓冲数组
      extern uint8_t Usart1_ReadCount;	//串口1 接收字节计数
      
      //判断否接收完一帧数据
      uint8_t Usart_WaitReasFinish(void)
      {
          static uint16_t Usart_LastReadCount = 0;//记录上次的计数值
          if(Usart1_ReadCount == 0)
          {
              Usart_LastReadCount = 0;
              return 1;//表示没有在接收数据
          }
          if(Usart1_ReadCount == Usart_LastReadCount)//如果这次计数值等于上次计数值
          {
              Usart1_ReadCount = 0;
              Usart_LastReadCount = 0;
              return 0;//已经接收完成了
          }
          Usart_LastReadCount = Usart1_ReadCount;
          return 2;//表示正在接受中
      }
      
    16. 🔧 通过发送数据调参

    17. 重定向fputc
    18. typedef struct __FILE FILE;
      
      int fputc(int ch, FILE *stream)
      {
          HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
          return ch;
      }
      
    19. mian.c中添加此句形成波形。
    20.  printf("MotorSpeed:%f,\n", MotorSpeed);     //根据自己的变量名进行修改
      
    21. 串口输出栏发送
      {"p":0.00,"i":0.00,"d":0.00,"target_val":0.00}改变Kp、Ki、Kd和target_val的值,实现调试效果
    22. 串口调参

    23. 📊 验证效果

    24. 波形示例
      波形示例

    作者:Weizzzzzzz

    物联沃分享整理
    物联沃-IOTWORD物联网 » PID算法原理详解及其实践指南

    发表回复