【STM32】单级与串级PID控制的C语言实现

【STM32】单级与串级PID的C语言实现

  • 前言
  • PID理论
    • 什么是PID
    • PID计算过程
    • PID计算公式
    • Pout、Iout、Dout的作用
    • 单级PID与串级PID
  • PID应用
    • 单级PID
    • 串级PID

前言

笔者最近在学习PID控制器,本文基于Blog做以总结。CSDN上已有大量PID理论知识的优秀文章,因此本文将略写理论部分,重点放在应用。

PID理论

什么是PID

PID(比例-积分-微分)控制器是一种广泛用于自动控制系统的闭环反馈控制器,能够实现系统的稳定、快速和精确控制。

PID计算过程

单级PID
在这里插入图片描述
串级PID
串级PID分为外环和内环,是两个单级PID嵌套构成的

在这里插入图片描述

PID计算公式

连续型
在这里插入图片描述
离散型
在这里插入图片描述

Pout、Iout、Dout的作用

  • Pout:主要负责控制,使反馈量快速接近目标值,但可能引起振荡
  • Iout:消除稳态误差,但会增加超调
  • Dout:提供阻尼,抑制振荡和超调,但可能降低响应速度

单级PID与串级PID

我们来思考一下:为什么自动控制系统中要引入PID?能否直接控制目标值?先来看个常见的PID应用例子

例子一:水阀控制水量
在这个例子中,目标值是水槽的水量(也即水面高度,量纲为长度,单位为m)。而可直接控制的变量是水阀的开度(即水流速度,量纲为速度,单位为m/s)。
注意,控制量相较于目标值为低一阶物理量,故我们无法直接控制高阶物理量(水面高度),只能控制低阶物理量(水流速度)。
(此时因为控制量与目标值只差一阶,所以单级PID即可。假设我们不能直接控制水流速度,只能直接控制水流的加速度,那么就需要串级PID控制了)
因此引入PID控制器,为了更好的通过控制低阶物理量完成对高阶物理量的控制
ps:水面高度和水体积呈线性关系,水阀开度和水流速也是线性相关,因此通过合理调节PID参数,系统可以自适应这种内含的线性关系

例子二:循迹小车

在这个例子中,目标是控制小车位置,目标值是小车目标位置,而可直接控制的变量是电机电流(可以理解为加速度或力)。由于电流(加速度)与位置之间相差两阶,因此需要使用串级PID控制

具体做法:

  • 外环PID:根据目标位置和当前实际位置的误差,输出一个目标速度给内环PID
  • 内环PID:根据目标速度和当前实际速度的误差,输出控制量(如PWM占空比)来给执行器

这种串级PID控制方法,通过外环控制速度、调节位置,内环控制PWM、调节速度,从而实现了精准的循迹控制

PID应用

单级PID

pid.h

#ifndef __PID_H__
#define __PID_H__

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

//首先定义PID结构体用于存放一个PID的数据
typedef struct
{
   	float kp, ki, kd; //三个系数
    float error, lastError; //误差、上次误差
    float integral, maxIntegral; //积分、积分限幅
    float output, maxOutput; //输出、输出限幅
}PID;

void PID_Init(PID *pid, float p, float i, float d, float maxI, float maxOut);
void PID_Calc(PID *pid, float reference, float feedback);

#endif

pid.c

#include "pid.h"

PID speed_pid_speed = {0}; //定义并初始化一个PID结构体变量,单级速度环

//初始化pid参数的函数
void PID_Init(PID *pid, float p, float i, float d, float maxI, float maxOut)
{
    pid->kp = p;
    pid->ki = i;
    pid->kd = d;
    pid->maxIntegral = maxI;
    pid->maxOutput = maxOut;
}
 
//进行一次PID控制器的计算,更新控制量:pid->output
//参数为(pid结构体,目标值,反馈值),计算结果放在pid结构体的output成员中
void PID_Calc(PID *pid, float reference, float feedback)
{
 	//更新数据
    pid->lastError = pid->error; //将旧error存起来
    pid->error = reference - feedback; //计算新error
    //计算比例
    float pout = pid->error * pid->kp;
    //计算积分
    pid->integral += pid->error * pid->ki;
	//计算微分
    float dout = (pid->error - pid->lastError) * pid->kd;
    //积分限幅
    if(pid->integral > pid->maxIntegral) pid->integral = pid->maxIntegral;
    else if(pid->integral < -pid->maxIntegral) pid->integral = -pid->maxIntegral;
    //计算输出
    pid->output = pout+dout + pid->integral;
    //输出限幅
    if(pid->output > pid->maxOutput) pid->output =  pid->maxOutput;
    else if(pid->output < -pid->maxOutput) pid->output = -pid->maxOutput;
}

my_main.c

void setup(void)
{
	PID_Init(&speed_pid,1,0,0,0,1000);	//举例
}

void loop(void)
{
	//更新被控对象反馈值和目标值(反正要不断更新、获取
	PID_Calc(&speed_pid,targetValue,feedbackValue);
	设置执行器输出大小(speed.output);//在控制电机转速的单级速度环pid中,控制执行器的操作为set_pwm(speed.output);
	HAL_Delay(10);	//控制采样周期,防止频繁计算导致系统抖动与资源消耗过高(根据不同情况合理配置即可
}

串级PID

pid.h

#ifndef __PID_H__
#define __PID_H__

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

//首先定义单级PID结构体
typedef struct
{
   	float kp, ki, kd; //三个系数
    float error, lastError; //误差、上次误差
    float integral, maxIntegral; //积分、积分限幅
    float output, maxOutput; //输出、输出限幅
}PID;

//再定义串级PID的结构体,嵌套两个单级PID
typedef struct
{
    PID inner; //内环
    PID outer; //外环
    float output; //串级输出,等于inner.output
}CascadePID;

void PID_Init(PID *pid, float p, float i, float d, float maxI, float maxOut);
void PID_Calc(PID *pid, float reference, float feedback);
void PID_CascadeCalc(CascadePID *pid, float outerRef, float outerFdb, float innerFdb);

#endif

pid.c

//初始化pid参数的函数
void PID_Init(PID *pid, float p, float i, float d, float maxI, float maxOut)
{
    pid->kp = p;
    pid->ki = i;
    pid->kd = d;
    pid->maxIntegral = maxI;
    pid->maxOutput = maxOut;
}

//进行一次PID控制器的计算,更新控制量:pid->output
//参数为(pid结构体,目标值,反馈值),计算结果放在pid结构体的output成员中
void PID_Calc(PID *pid, float reference, float feedback)
{
 	//更新数据
    pid->lastError = pid->error; //将旧error存起来
    pid->error = reference - feedback; //计算新error
    //计算比例
    float pout = pid->error * pid->kp;
    //计算积分
    pid->integral += pid->error * pid->ki;
	//计算微分
    float dout = (pid->error - pid->lastError) * pid->kd;
    //积分限幅
    if(pid->integral > pid->maxIntegral) pid->integral = pid->maxIntegral;
    else if(pid->integral < -pid->maxIntegral) pid->integral = -pid->maxIntegral;
    //计算输出
    pid->output = pout+dout + pid->integral;
    //输出限幅
    if(pid->output > pid->maxOutput) pid->output =  pid->maxOutput;
    else if(pid->output < -pid->maxOutput) pid->output = -pid->maxOutput;
}

//串级PID的计算函数(调用两次单级PID计算,先算外环再算内环,串级cascade_pid->output被更新
//参数(串级PID结构体,外环目标值,外环反馈值,内环反馈值)
void PID_CascadeCalc(CascadePID *pid, float outerRef, float outerFdb, float innerFdb)
{
    PID_Calc(&pid->outer, outerRef, outerFdb); //先计算外环
    PID_Calc(&pid->inner, pid->outer.output, innerFdb); //再计算内环
    pid->output = pid->inner.output; //控制量 = 内环输出 = 串级PID的输出(所以
}

my_main.c
内环 PID 控制频率应高于外环,以保证内环能快速响应动态变化。建议避免内外环在同一周期更新,最好通过定时中断分别计算单级和串级 PID 控制,确保系统稳定性和响应速度

void setup(void)
{
	CascadePID cascade_pid = {0}; //创建串级PID结构体变量
 	PID_Init(&cascade_pid.inner, 10, 0, 0, 0, 1000); //初始化内环参数
    PID_Init(&cascade_pid.outer, 5, 0, 5, 0, 100); //初始化外环参数	
}

void loop(void)
{
	//更新、获取外环目标值、外环反馈值、内环反馈值
    PID_CascadeCalc(&cascade_pid, outerTarget, outerFeedback, innerFeedback); //进行串级PID计算
    设定执行机构输出大小(cascade_pid.output);	//在循迹控制中,控制执行输出为set_pwm(cascade_pid.output)
	HAL_Delay(10);	//控制采样周期,防止频繁计算导致系统抖动与资源消耗过高(根据不同情况合理配置即可    
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/876271.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

短信验证码倒计时 (直接复制即可使用) vue3

需求&#xff1a; 要实现一个获取验证码的需求&#xff0c;点击获取验证码60秒内不可以重复点击&#xff0c;方式有两种可以直接复制使用&#xff1b; 效果图 实现方案 方案1 (单个文件内使用比较推荐) <el-button :disabled"codeDisabled" click.stop"h…

【在Linux世界中追寻伟大的One Piece】网络命令|验证UDP

目录 1 -> Ping命令 2 -> Netstat命令 3 -> Pidof命令 4 -> 验证UDP-Windows作为client访问Linux 4.1 -> UDP client样例 1 -> Ping命令 Ping命令是一种网络诊断工具&#xff0c;它使用ICMP(Internet Control Message Protocol&#xff0c;互联网控制消…

redis常见的数据类型?

参考&#xff1a;一文读懂Redis五种数据类型及应用场景 - 知乎 (zhihu.com) String 类型 String 类型&#xff1a;Redis 最基本的数据类型&#xff0c;它是二进制安全的&#xff0c;意味着你可以用它来存储任何类型的数据&#xff0c;如图片、序列化对象等。使用场景&#xff…

Qt入门教程---项目创建全过程内存泄漏解释

目录 1.创建项目的说明 2.代码介绍说明 2.1文件分类介绍 2.2sources文件 2.3widget.ui文件 2.4widget.h文件 2.5中间文件 2.6.pro文件 3.打印输出hello world 3.1图形化界面生成控件 3.2代码生成控件 3.3打印结果展示 4.对于内存泄露的讨论 4.1对象树 4.2与栈开辟…

一图读懂 若依后端

一图读懂 若依后端 关注我&#xff0c;评论区评论就能获得思维导图本体文件啦&#x1f604;。如果你愿意关注我的掘金就更好啦宝&#x1f60d;&#xff0c;因为我掘金文章就一内内人看&#xff0c;想引流&#x1f60b; https://juejin.cn/user/1942157160101860本图是对若依后…

基础GAN生成式对抗网络(pytorch实验)

&#xff08;Generative Adversarial Network&#xff09; 一、理论 https://zhuanlan.zhihu.com/p/307527293?utm_campaignshareopn&utm_mediumsocial&utm_psn1815884330188283904&utm_sourcewechat_session 大佬的文章中的“GEN的本质”部分 二、实验 1、数…

Java | Leetcode Java题解之第403题青蛙过河

题目&#xff1a; 题解&#xff1a; class Solution {public boolean canCross(int[] stones) {int n stones.length;boolean[][] dp new boolean[n][n];dp[0][0] true;for (int i 1; i < n; i) {if (stones[i] - stones[i - 1] > i) {return false;}}for (int i 1…

Oracle 11gR2打PSU补丁详细教程

1 说明 Oracle的PSU&#xff08;Patch Set Update&#xff09;补丁是Oracle公司为了其数据库产品定期发布的更新包&#xff0c;通常每季度发布一次。PSU包含了该季度内收集的一系列安全更新&#xff08;CPU&#xff1a;Critical Patch Update&#xff09;以及一些重要的错误修…

效率神器来了:AI工具手把手教你快速提升工作效能

随着科技的进步&#xff0c;AI工具已经成为提升工作效率的关键手段。本文将介绍一些实用的AI工具和方法&#xff0c;帮助你自动化繁琐的重复性任务、优化数据管理、促进团队协作与沟通&#xff0c;并提升决策质量。 背景&#xff1a;OOP AI-免费问答学习交流-GPT 自动化重复性任…

【Linux】【Vim】Vim 基础

Vim/Gvim 基础 文本编辑基础编辑操作符命令和位移改变文本重复改动Visual 模式移动文本(复制、粘贴)文本对象替换模式 光标移动以 word 为单位移动行首和行尾行内指定单字符移动到匹配的括号光标移动到指定行滚屏简单查找 /string标记 分屏vimdiff 文本编辑 基础编辑 Normal 模…

【网络安全的神秘世界】渗透测试基础

&#x1f31d;博客主页&#xff1a;泥菩萨 &#x1f496;专栏&#xff1a;Linux探索之旅 | 网络安全的神秘世界 | 专接本 | 每天学会一个渗透测试工具 渗透测试基础 基于功能去进行漏洞挖掘 1、编辑器漏洞 1.1 编辑器漏洞介绍 一般企业搭建网站可能采用了通用模板&#xff…

app抓包 chrome://inspect/#devices

一、前言&#xff1a; 1.首先不支持flutter框架&#xff0c;可支持ionic、taro 2.初次需要翻墙 3.app为debug包&#xff0c;非release 二、具体步骤 1.谷歌浏览器地址&#xff1a;chrome://inspect/#devices qq浏览器地址&#xff1a;qqbrowser://inspect/#devi…

Lombok:Java开发者的代码简化神器【后端 17】

Lombok&#xff1a;Java开发者的代码简化神器 在Java开发中&#xff0c;我们经常需要编写大量的样板代码&#xff0c;如getter、setter、equals、hashCode、toString等方法。这些代码虽然基础且必要&#xff0c;但往往占据了大量开发时间&#xff0c;且容易在属性变更时引发错误…

华为OD机试 - 计算误码率(Python/JS/C/C++ 2024 E卷 100分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加入华为OD刷题交流群&#xff0c;…

怎么将几个pdf合成为一个?把几个PDF合并成为一个的8种方法

怎么将几个pdf合成为一个&#xff1f;将多个PDF文件合并成一个整体可以显著提高信息整合的效率&#xff0c;并简化文件的管理与传递。例如&#xff0c;将不同章节的电子书合成一本完整的书籍&#xff0c;或者将多个部门的报告整合成一个统一的文档&#xff0c;可以使处理流程变…

CCS811二氧化碳传感器详解(STM32)

目录 一、介绍 二、传感器原理 1.原理图 2.引脚描述 3.工作原理介绍 三、程序设计 main.c文件 ccs811.h文件 ccs811.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 CCS811模块是一种气体传感器&#xff0c;可以测量环境中TVOC(总挥发性有机物质)浓度和eCO2…

6.接口测试加密接口(Jmeter/工具/函数助手对话框、Beanshell脚本)

一、接口测试加密接口&#xff0c;签名接口 1.加密算法&#xff1a; 可以解密的&#xff1a; 对称式加密&#xff08;私钥加密&#xff09;&#xff1a;AES&#xff0c;DES&#xff0c;Base64 https://www.bejson.com 非对称加密&#xff08;双…

Fisco Bcos 2.11.0通过网络和本地二进制文件搭建单机节点联盟链网络(搭建你的第一个区块链网络)

Fisco Bcos 2.11.0通过网络和本地二进制文件搭建单机节点联盟链网络(搭建你的第一个区块链网络) 文章目录 Fisco Bcos 2.11.0通过网络和本地二进制文件搭建单机节点联盟链网络(搭建你的第一个区块链网络)前言一、Ubuntu依赖安装二、创建操作目录, 下载build_chain.sh脚本2.1 先…

Linux-Swap分区使用与扩容

一、背景 在Linux系统中&#xff0c;swap空间&#xff08;通常称为swap分区&#xff09;是一个用于补充内存资源的重要组件。当系统的物理RAM不足时&#xff0c;Linux会将一部分不经常使用的内存页面移动到硬盘上的swap空间中&#xff0c;这个过程被称为分页&#xff08;paging…

【JavaEE初阶】多线程(4)

欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 目录 线程安全的 第四个原因 代码举例: 分析原因 解决方法 方法1 方法2 wait(等待)和notify(通知) wait和sleep区别 线程安全的 第四个原因 内存可见性,引起的线程安全问…