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

news2024/9/22 9:59:48

【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.coloradmin.cn/o/2139366.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于HPLC的低压电力采集方案

1. 组网部署 2. 组网部件 3. 原理

✔2848. 与车相交的点

代码实现&#xff1a; 方法一&#xff1a;哈希表 #define fmax(a, b) ((a) > (b) ? (a) : (b))int numberOfPoints(int **nums, int numsSize, int *numsColSize) {int hash[101] {0};int max 0;for (int i 0; i < numsSize; i) {max fmax(max, nums[i][1]);for …

基于SSM+Vue+MySQL的新生报到管理系统

系统展示 用户界面 管理员界面 系统背景 在当今高等教育日益普及的背景下&#xff0c;新生报到管理成为高校日常管理中的重要环节。为了提升报到效率、优化管理流程并确保数据的准确性与安全性&#xff0c;我们设计并实现了一个基于SSM&#xff08;SpringSpring MVCMyBatis&…

JavaScript高级——作用域和作用链

1、概念理解&#xff1a; —— 就是一块“地盘”&#xff0c;一个代码所在的区域 —— 静态的&#xff08;相对于上下文对象&#xff09;&#xff0c;在编写代码时就确定了 2、分类 ① 全局作用域 ② 函数作用域 ③ 没有块作用域&#xff08;ES6有了&#xff09; 3、作用 …

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…

C#开发基础之单例模式下的集合数据,解决并发访问读写冲突的问题

1. 前言 在C#中&#xff0c;使用单例模式管理集合数据时&#xff0c;如果多线程同时访问集合&#xff0c;容易产生并发访问的读写冲突问题。单例模式下集合数据的并发访问读写冲突是如何产生的&#xff1f; 单例模式确保一个类在整个应用运行期间只有一个实例&#xff0c;这使…

CSP-J 算法基础 图论

文章目录 前言图的简介1. **图的定义**2. **图的类型**3. **图的表示方法**a. **邻接矩阵&#xff08;Adjacency Matrix&#xff09;**b. **邻接表&#xff08;Adjacency List&#xff09;** 4. **图的基本操作**5. **图的遍历**6. **图的应用**7. **图的算法** 出度与入度1. *…

Android实现关机和重启功能

文章目录 需求场景需求场景经历 一、解决思路和方案实际困难点情况普遍思路用Shell 命令实现应用和系统联调遇到的问题 个人解决思路和方案 二、代码跟踪系统实现的关机、重启界面GlobalActionsDialogLite.java 创建关机、重启菜单createActionItems()shutdownAction GlobalAct…

时序数据库 TDengine 的入门体验和操作记录

时序数据库 TDengine 的学习和使用经验 什么是 TDengine &#xff1f;什么是时序数据 &#xff1f;使用RPM安装包部署默认的网络端口 TDengine 使用TDengine 命令行&#xff08;CLI&#xff09;taosBenchmark服务器内存需求删库跑路测试 使用体验文档纠错 什么是 TDengine &…

GEE:连续变化检测与分类(Continuous Change Detection and Classification, CCDC)教程

连续变化检测与分类&#xff08;Continuous Change Detection and Classification, CCDC&#xff09;是一种土地变化监测算法&#xff0c;旨在对卫星数据的时间序列进行操作&#xff0c;特别是Landsat数据。CCDC包括两个部分&#xff0c;其一是变化检测算法&#xff08;Change …

Mybatis中Like模糊查询三种处理方式

目录 Mybatis中Like模糊查询三种处理方式 1.通过单引号拼接${} 1&#xff09;mapper接口 2&#xff09;Mapper.xml 3&#xff09;测试代码 4) 测试结果 2.通过concat()函数拼接(个人推荐使用这种) 1&#xff09;mapper接口 2&#xff09;Mapper.xml 3&#xff09;测试代码 4) 测…

C语言-整数和浮点数在内存中的存储-详解-下

C语言-整数和浮点数在内存中的存储-详解-下 1.前言2.浮点数2.1IEEE 754 标准2.2存储格式存储细节取出 3.相关代码的解释 1.前言 在C语言-整数和浮点数在内存中的存储-详解-上中&#xff0c;我通过一个简单的例子展示了整数和浮点数在内存中的存储差异&#xff0c;并详细介绍了…

Java重修笔记 第五十六天 坦克大战(六)多线程基础 - 线程同步、死锁

多线程同步机制 多线程编程中&#xff0c;一些敏感数据可能会被多个线程同时访问造成数据混乱&#xff08;例如票数&#xff09;&#xff0c;使用线程同步机制&#xff0c;通过锁对象&#xff08;对象实例或类实例&#xff09;的方式来保证该段代码在任意时刻&#xff0c;最多…

TCP socket

TCP的socket和UDP大同小异&#xff0c;基本的代码结构都是相同的。一些相同的接口本文就不赘述了&#xff0c;例如&#xff0c;socket,bind&#xff0c;有需要看这篇文章UDP socket 服务端server 两步&#xff1a;初始化服务端&#xff0c;运行服务端 初始化服务端 创建soc…

Java项目基于docker 部署配置

linux新建文件夹 data cd datatouch Dockerfilesudo vim Dockerfile# 使用一个基础的 Java 镜像&#xff08;根据自己项目中使用的是什么jdk版本设置&#xff0c;用于拉取执行jar包的jdk环境&#xff09; FROM openjdk:8# 指定工作目录 VOLUME /data# 复制应用程序的 JAR 文件…

Redis模拟消息队列实现异步秒杀

目录 一、消息队列含义 二、Redis实现消息队列 1、基于List的结构模拟实现消息队列 2、基于PubSub的消息队列 3、基于Stream的消息队列 4、基于Stream的消息队列- 消费者组 一、消息队列含义 消息队列&#xff08;Message Queue&#xff09;&#xff0c;字面意思就是存放…

基于SpringBoot的招生宣传管理系统【附源码】

基于SpringBoot的招生宣传管理系统&#xff08;源码L文说明文档&#xff09; 目录 4 系统设计 4.1 系统概述 4.2系统功能结构设计 4.3数据库设计 4.3.1数据库E-R图设计 4.3.2 数据库表结构设计 5 系统实现 5.1管理员功能介绍 5.1.1管理员登录 …

【JavaScript】LeetCode:36-40

文章目录 36 两数相加37 删除链表的倒数第n个节点38 两两交换链表中的节点39 k个一组翻转链表40 随机链表的复制 36 两数相加 创建一个新的链表&#xff08;哨兵节点指向&#xff09;&#xff0c;这个链表用来表示两个数相加后的和。从个位开始相加&#xff0c;每次都向新链表尾…

PCL 点云随机渲染颜色

目录 一、概述 1.1原理 1.2实现步骤 1.3 应用场景 二、代码实现 2.1关键函数 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算法与项目实战案例汇总&#xff08;长期更新&#xff09; 一、概述 本文将介绍如何使用PCL库…

PMP–一、二、三模–分类–14.敏捷–技巧–项目生命周期

文章目录 技巧项目生命周期 一模14.敏捷--项目生命周期--原型法--迭代型生命周期&#xff0c;通过连续的原型或概念验证来改进产品或成果。每个新的原型都能带来新的干系人新的反馈和团队见解。题目中明确提到需要反馈&#xff0c;因此原型法比较好用。23、 [单选] 一个敏捷团队…