当前位置: 首页 > ai >正文

PID控制学习(位置式,增量式,算法优化,多环串级PID)

本笔记只针对应用,对原理及公式无太多理解

描述

PID是比例(Proportional)、积分(Integral)、微分(Differential)的缩写
PID是一种闭环控制算法,它动态改变施加到被控对象的输出值(Out),使得被控对象某一物理量的实际值(Actual),能够快速、准确、稳定地跟踪到指定的目标值(Target)
PID是一种基于误差(Error)调控的算法,其中规定:误差=目标值-实际值,PID的任务是使误差始终为0
PID对被控对象模型要求低,无需建模,即使被控对象内部运作规律不明确,PID也能进行调控

闭环(Closed Loop):控制器输出值给被控对象,同时获取被控对象的反馈,控制器知道被控对象的执行状态,可以根据反馈修改输出值以优化控制

![[Pasted image 20250520171028.png]]

个人白话理解

P(比例):控制调整的速度
I(积分): 消除稳态误差 根据追踪时误差的积分 最终的结果会贴合目标值
D(微分):消除调整时候的超调
![[Pasted image 20250520171900.png]]

两种PID公式(位置式PID与增量式PID公式)

![[Pasted image 20250520181843.png]]

位置式PID程序实现

实现了电机定速控制,

Actual = Encoder_Get();  //通过编码器得出当前电机转速Errol1 = Errol0;
Errol0 = Target - Actual;if(Ki != 0)    //误差积分
{ErrolInt += Errol0;
}
else
{ErrolInt = 0;
}Out = Kp * Errol0 + Ki * ErrolInt + Kd * (Errol0 - Errol1);  //PID计算if(Out > 100)	Out = 100;  //输出限幅
if(Out < -100)	Out = -100;Motor_SetPWM(Out);	

增量式PID程序实现

Actual = Encoder_Get();Errol2 = Errol1;
Errol1 = Errol0;
Errol0 = Target - Actual;Out += Kp * (Errol0 - Errol1) + Ki * Errol0 + Kd * (Errol0 - 2 * Errol1 + Errol2);if(Out > 100)	Out = 100;
if(Out < -100)	Out = -100;Motor_SetPWM(Out);	

PID算法改进

以下都基础位置式PID改进

积分限幅:限制积分的幅度,防止积分深度饱和

Actual = Encoder_Get();Errol1 = Errol0;
Errol0 = Target - Actual;ErrolInt += Errol0;
if(ErrolInt > 500)	ErrolInt = 500;  //积分限幅
if(ErrolInt < -500)	ErrolInt = -500;Out = Kp * Errol0 + Ki * ErrolInt + Kd * (Errol0 - Errol1);if(Out > 100)	Out = 100; 
if(Out < -100)	Out = -100;if(motor_flag)
{Motor_SetPWM(Out);	
}
else
{Motor_SetPWM(0);
}

积分分离:误差小于一个限度才开始积分,反之则去掉积分部分

Actual += Encoder_Get();Errol1 = Errol0;
Errol0 = Target - Actual;if(fabs(Errol0) < 50)   //积分分离
{ErrolInt += Errol0;
}
else
{ErrolInt = 0;
}Out = Kp * Errol0 + Ki * ErrolInt + Kd * (Errol0 - Errol1);if(Out > 100)	Out = 100;
if(Out < -100)	Out = -100;Motor_SetPWM(Out);	

变速积分:根据误差的大小调整积分的速度

Actual += Encoder_Get();Errol1 = Errol0;
Errol0 = Target - Actual;float C = 1 / (0.2 * fabs(Errol0) + 1);  //变速积分  0.2是可以控制的ErrolInt += C * Errol0;Out = Kp * Errol0 + Ki * ErrolInt + Kd * (Errol0 - Errol1);if(Out > 100)	Out = 100;
if(Out < -100)	Out = -100;Motor_SetPWM(Out);	

微分先行:将对误差的微分替换为对实际值的微分

与传统PID控制相比,微分先行的特点:
传统PID:微分项是基于误差的变化率 (Target - Actual)的变化
微分先行:微分项是基于实际值的变化率 (Actual的变化),而不考虑目标值的变化

这种实现方式的优点:
当目标值(Target)突然变化时,不会产生过大的微分冲击
系统响应更加平滑,减少了超调和振荡
特别适合目标值频繁变化的场合

Actual1 = Actual;  // 保存上一次的实际值
Actual += Encoder_Get();  // 获取当前实际值Errol1 = Errol0;
Errol0 = Target - Actual;if(Ki != 0)
{ErrolInt += Errol0;
}
else
{ErrolInt = 0;
}// 微分项计算
DifOut = -Kd * (Actual - Actual1);  // 使用当前和上一次的实际值差计算微分Out = Kp * Errol0 + Ki * ErrolInt + DifOut;if(Out > 100)	Out = 100;
if(Out < -100)	Out = -100;Motor_SetPWM(Out);	

不完全微分:给微分项加入一阶惯性单元(低通滤波器)

Actual += Encoder_Get();
Actual += rand() % 41 - 20;Errol1 = Errol0;
Errol0 = Target - Actual;if(Ki != 0)
{ErrolInt += Errol0;
}
else
{ErrolInt = 0;
}float a = 0.9;
DifOut = (1-a) * Kd * (Errol0 - Errol1) + a * DifOut;  //对于噪声干扰进行滤波,主要是对Kd项的斜率突变进行滤波,
//使D项的数据变的更加平滑  a越接近1越平滑 , 此次计算只取这次计算数据的10%,90%用上次的输出  Out = Kp * Errol0 + Ki * ErrolInt + DifOut;if(Out > 100)	Out = 100;
if(Out < -100)	Out = -100;Motor_SetPWM(Out);	

输出偏移 输入死区

输出偏移:在非0输出时,给输出值加一个固定偏移
输入死区:误差小于一个限度时不进行调控

Actual += Encoder_Get();Errol1 = Errol0;
Errol0 = Target - Actual;		//检测目标与实际值的误差if(fabs(Errol0) < 5)	//输入死区设置
{Out = 0;
}
else
{if(Ki != 0){ErrolInt += Errol0;}else{ErrolInt = 0;}Out = Kp * Errol0 + Ki * ErrolInt + Kd * (Errol0 - Errol1);if(Out > 0.1)		//pwm要输出大于5的时候电机才能驱动的起来开始转动 , 所以强行给一个输出偏移{Out += 6;}else if(Out < 0.1){Out -= 6;}else{Out = 0;}if(Out > 100)	Out = 100;if(Out < -100)	Out = -100;	
}Motor_SetPWM(Out);	

多环串级PID实现

单环PID只能对被控对象的一个物理量进行闭环控制,而当用户需要对被控对象的多个维度物理量(例如:速度、位置、角度等)进行控制时,则需要多个PID控制环路,即多环PID,多个PID串级连接,因此也称作串级PID
多环PID相较于单环PID,功能上,可以实现对更多物理量的控制,性能上,可以使系统拥有更高的准确性、稳定性和响应速度

![[Pasted image 20250526110902.png]]

定位置控制单环与双环比较

单环跟双环对比:
1、可以规避位置死区,做到精准跟踪位置,避免调控误差
2、可以输出方向作用力抵抗外界干扰,速度环的积分累积反向作用力越大,被外界干扰响应又快又有劲
3、总体拥有更高的准确性、稳定性和响应速度

![[Pasted image 20250526154925.png]]

程序实现

先内环再外环

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "LED.h"
#include "Timer.h"
#include "Key.h"
#include "RP.h"
#include "Motor.h"
#include "Encoder.h"
#include "Serial.h"
#include "pid.h"/*电机测试*/uint8_t key_num;int16_t speed = 0;int16_t Location = 0;int16_t pwm_speed = 0;PID_t Inner = {.Kp = 0.3,.Ki = 0.5,.Kd = 0,.OutMax = 100,.OutMin = -100,};PID_t Outer = {.Kp = 0.4,.Ki = 0,.Kd = 0.2,.OutMax = 150,.OutMin = -150,};int main(void){OLED_Init();  // 初始化OLED屏幕Timer_Init();  // 初始化定时器Encoder_Init();  // 初始化编码器Key_Init();  // 初始化按键Motor_Init();  // 初始化电机RP_Init();  // 初始化RP(可能指电位器或其他旋转电位器)Serial_Init();  // 初始化串口通信OLED_Printf(0,0,OLED_8X16,"2*pid control");  // 在OLED上显示文本while (1){// key_num = Key_GetNum();  // 获取按键值// if(key_num == 1)// {// 	Outer.Target += 10;  // 如果按键1被按下,则目标值增加10// }// if(key_num == 2)// {// 	Outer.Target -= 10;  // 如果按键2被按下,则目标值减少10// }// if(key_num == 3)// {// 	Outer.Target = 0;  // 如果按键3被按下,则目标值设为0// }// if(key_num == 4)// {// 	Outer.Target = 100;  // 如果按键4被按下,则目标值设为100// }// Inner.Kp = RP_GetValue(1) / 4095.0 * 2;  // 获取RP值并转换为Kp值// Inner.Ki = RP_GetValue(2) / 4095.0 * 2;  // 获取RP值并转换为Ki值// Inner.Kd = RP_GetValue(3) / 4095.0 * 2;  // 获取RP值并转换为Kd值// Inner.Target = RP_GetValue(4) / 4095.0 * 300 - 150;  // 获取RP值并转换为目标值// OLED_Printf(0,16,OLED_8X16,"kp:%4.2f",Inner.Kp);  // 在OLED上显示Kp值// OLED_Printf(0,32,OLED_8X16,"ki:%4.2f",Inner.Ki);  // 在OLED上显示Ki值// OLED_Printf(0,48,OLED_8X16,"kd:%4.2f",Inner.Kd);  // 在OLED上显示Kd值// OLED_Printf(64,16,OLED_8X16,"Tar:%+04.0f",Inner.Target);  // 在OLED上显示目标值// OLED_Printf(64,32,OLED_8X16,"Act:%+04.0f",Inner.Actual);  // 在OLED上显示实际值// OLED_Printf(64,48,OLED_8X16,"Out:%+04.0f",Inner.Out);  // 在OLED上显示输出值// OLED_Update();  // 更新OLED显示// Serial_Printf("%f,%f,%f\r\n",Inner.Target,Inner.Actual,Inner.Out);  // 通过串口发送目标值、实际值和输出值// OuterKp = RP_GetValue(1) / 4095.0 * 2;  // 获取RP值并转换为Kp值// OuterKi = RP_GetValue(2) / 4095.0 * 2;  // 获取RP值并转换为Ki值// OuterKd = RP_GetValue(3) / 4095.0 * 2;  // 获取RP值并转换为Kd值Outer.Target = RP_GetValue(4) / 4095.0 * 816 - 408;  // 获取RP值并转换为目标值OLED_Printf(0,16,OLED_8X16,"kp:%4.2f",Outer.Kp);  // 在OLED上显示Kp值OLED_Printf(0,32,OLED_8X16,"ki:%4.2f",Outer.Ki);  // 在OLED上显示Ki值OLED_Printf(0,48,OLED_8X16,"kd:%4.2f",Outer.Kd);  // 在OLED上显示Kd值OLED_Printf(64,16,OLED_8X16,"Tar:%+04.0f",Outer.Target);  // 在OLED上显示目标值OLED_Printf(64,32,OLED_8X16,"Act:%+04.0f",Outer.Actual);  // 在OLED上显示实际值OLED_Printf(64,48,OLED_8X16,"Out:%+04.0f",Outer.Out);  // 在OLED上显示输出值OLED_Update();  // 更新OLED显示Serial_Printf("%f,%f,%f\r\n",Outer.Target,Outer.Actual,Outer.Out);  // 通过串口发送目标值、实际值和输出值}}void TIM1_UP_IRQHandler(void){static uint8_t count1,count2;// 检查是否发生了TIM1的更新中断if (TIM_GetITStatus(TIM1, TIM_IT_Update) == SET){// 更新按键状态Key_Tick();// 更新count1计数器if(++count1 >= 40){count1 = 0;// 获取编码器速度speed = Encoder_Get();// 更新位置Location += speed;// 更新内部PID控制器的实际值Inner.Actual = speed;// 更新内部PID控制器PID_Update(&Inner);// 设置电机PWMMotor_SetPWM(Inner.Out);	}// 更新count2计数器if(++count2 >= 40){count2 = 0;// 更新外部PID控制器的实际值Outer.Actual = Location;// 更新外部PID控制器PID_Update(&Outer);// 更新内部PID控制器的目标值Inner.Target = Outer.Out;}// 清除TIM1的更新中断标志位TIM_ClearITPendingBit(TIM1, TIM_IT_Update);}}
http://www.xdnf.cn/news/9120.html

相关文章:

  • LitCTF 2025 Robbie Wanna Revenge
  • 并发的产生及对应的解决方案之实例举证
  • Java 中经常犯的错误
  • 2025年5月26日第一轮
  • 【springboot项目部署】打包部署
  • 矩阵链乘法问题
  • vae 视频截图 复习 gans和vae的原理区别
  • JVM垃圾回收器详细介绍
  • 注解的使用和自定义
  • Composer 常规操作说明与问题处理
  • 【部署】读取制度类txt文件导入dify的父子分段知识库
  • Kubernetes 1.33您需要了解的和升级新功能
  • 爬虫学习-Scrape Center spa6 超简单 JS 逆向
  • 二叉树遍历
  • 打破壁垒:国内软件业产品与技术割裂困局及工程师产品思维重塑
  • 无网络docker镜像迁移
  • OSC协议简介、工作原理、特点、数据的接收和发送
  • 5月26日day37打卡
  • 【大模型Pre-Training实战总结】实现Qwen3增量预训练,Lora训练与合并
  • 修改mysql 数据库密码记录
  • MySQL数据库零基础入门教程:从安装配置到数据查询全掌握
  • 2025年AIR SCI1区TOP,具有新变异策略和外部存档机制mLSHADE-SPACMA+数值优化与点云配准,深度解析+性能实测
  • 【2025】harbor仓库搭建
  • MAR:无需量化的掩码自回归图像生成模型
  • Windows Server 2016 下封禁端口规避高危漏洞的测试实践
  • 通过chrome插件自动生成博客评论,高效发外链
  • 15.2【基础项目】使用 TypeScript 实现密码显示与隐藏功能
  • wsl2 安装 nodejs
  • 人工智能与教育科技:2025年个性化学习的新模式
  • (C++17) 未捕获异常 uncaught_exceptions