PID代码实现

在之前的一篇文章中讨论了PID控制器的数学原理和调试技巧,本文续接,讨论PID在微控制器上使用C++语言实现

分类

  • 位置式PID

    $$
    V[n]=K_p \cdot e[n] + K_i \cdot \sum ^n_{\tau = 0} e[\tau] + K_d \cdot (e[n]-e[n-1])
    $$

    从公式上,可以显式的看出位置式PID的公式中带有积分成分(累加),也就是过去的所有状态误差都对输出造成影响

  • 增量式PID

    在位置式PID的基础上,对本次值和上次值做差分可计算得:
    $$
    \Delta V = V[n]-V[n-1] \\
    = K_p \cdot (e[n]-e[n-1]) + K_i \cdot e[n] + K_d \cdot (e[n]-2e[n-1]+e[n-2])
    $$

    显然增量式PID算法中,由于采用的是“增量”,所以计算过程中使用到的过去状态量只有本次误差采样、上次误差采样和上上次误差采样,在此之前的数据没有影响

本质理解与应用

  • 增量式PID
    • 适用对象:本身带有积分性质的受控目标
    • 实际应用:对于步进电机,电机工作在对固定角度的锁定状态(除非失步),当是对步进电机的目标位置进行控制时,步进电机本身相当于一个积分器;而且对步进电机的驱动方式,就是输入和转角正比的脉冲个数,所以增量式PID适用该场景
  • 位置式PID
    • 适用对象:无记忆系统
    • 实际应用:对于直流电机,电机的转速和电机的输入电压是固定无记忆函数关系(稳态),所以控制直流电机旋转,应该采用位置式PID电机

代码实现

增量式PID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template<typename T>
T PID<T>::PID_updateDelta(T status){
T errNow = target_ - status; //目前误差值

T delta = //输出增量
K_p * (errNow - error_Pre) + //比例P
K_i * errNow + //积分I
K_d * (errNow - 2*error_Pre + error_PPre); //微分D

if(delta>max_) delta = max_; //输出限幅
else if(delta<min_) delta = min_;

error_PPre = error_Pre; //误差记忆
error_Pre = errNow;

return delta; //返回结果
}

位置式PID

位置式就是在增量式的基础上进行了状态存储,累加积分

1
2
3
4
5
6
7
8
9
10
template<typename T>
T PID<T>::PID_updateStatus(T status){

output_pre += this->PID_updateDelta(status);

if(output_pre>max_) output_pre = max_; //输出限幅
else if(output_pre<min_) output_pre = min_;

return output_pre;
}

完整工程代码

为了提高代码的可移植性,将PID控制器封装成了模板类,并且使用合适的预编译控制切换增量式PID和位置式PID

工程代码已经上传到笔者的Github仓库:嵌入式算法,欢迎白嫖

  • 头文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/* 
* PID.hpp - The C++ head file of PID controller
* NOTE: This file is based on C++11
*
* Copyright (c) 2020-, FOSH Project
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes mail
* 2020-06-14 StudyHooligen first version 2373180028@qq.com
*/
#ifndef _PID_H_
#define _PID_H_

//#define PID_use_integral //若使用位置式PID则开启该预编译选项

template<typename T>
class PID
{

private:
/************ 内部参数 *************/
T K_p = 0; //比例增益
T K_i = 0; //积分增益
T K_d = 0; //微分增益

T target_ = 0; //输出目标值

T error_Pre = 0; //上一次误差
T error_PPre = 0; //上上次误差

#ifdef PID_use_integral
T output_pre = 0; //上次输出值
#endif

T max_ = 65536; //PID输出最大值
T min_ = 0; //PID输出最小值

public:
/*********** 模块接口 ***********/
typedef PID * PIDptr; //模块指针

/* 概要:默认构造函数
* 入参:无
*/
PID();

/* 概要:带参构造函数
* 入参:
* P: 比例系数
* I:积分系数
* D:微分系数
*/
PID(T P,T I,T D,T outMin,T outMax);

/* 概要:默认析构函数
* 入参:无
*/
~PID();

/* 概要:改变PID实例参数
* 入参:
* target:目标值
* 回参:参数设置成功或失败
*/
bool PID_setTarget(T target);

/* 概要:改变PID实例参数
* 入参:
* P: 比例系数
* I:积分系数
* D:微分系数
* 回参:参数设置成功或失败
*/
bool PID_setParam(T P,T I,T D);

/* 概要:改变PID实例限幅值
* 入参:
* target:目标值
* 回参:参数设置成功或失败
*/
bool PID_setThreshold(T outMin,T outMax);

/* 概要:求取PID实例增量值
* 入参:
* status:目标状态测量值
* 回参:增量值
*/
T PID_updateDelta(T status);

/* 概要:求取PID实例输出值
* 入参:
* status:目标状态测量值
* 回参:状态值
*/
T PID_updateStatus(T status);

/* 概要:获取P参数
* 入参:无
* 回参:P参数
*/
T PID_getKp()
{
return K_p;
}

/* 概要:获取P参数
* 入参:无
* 回参:P参数
*/
T PID_getKi()
{
return K_i;
}

/* 概要:获取P参数
* 入参:无
* 回参:P参数
*/
T PID_getKd()
{
return K_d;
}

};

#endif

  • 源文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/* 
* PID.cpp - The C++ source file of PID controller
* NOTE: This file is based on C++11
*
* Copyright (c) 2020-, FOSH Project
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes mail
* 2020-06-14 StudyHooligen first version 2373180028@qq.com
*/
#include "PID/PID.hpp"

template<typename T>
PID<T>::PID(T P,T I,T D,T outMax,T outMin)
:K_p(P),K_i(I),K_d(D),
max_(outMax),min_(outMin){
}

template<typename T>
bool PID<T>::PID_setTarget(T target){
target_ = target;
return true;
}

template<typename T>
bool PID<T>::PID_setParam(T P,T I,T D){
this->K_p = P;
this->K_i = I;
this->K_d = D;
return true;
}

template<typename T>
bool PID<T>::PID_setThreshold(T outMin,T outMax){
max_ = outMax;
min_ = outMin;
return true;
}

template<typename T>
T PID<T>::PID_updateDelta(T status){
T errNow = target_ - status; //目前误差值

T delta = //输出增量
K_p * (errNow - error_Pre) + //比例P
K_i * errNow + //积分I
K_d * (errNow - 2*error_Pre + error_PPre); //微分D

if(delta>max_) delta = max_; //输出限幅
else if(delta<min_) delta = min_;

error_PPre = error_Pre; //误差记忆
error_Pre = errNow;

return delta; //返回结果
}

#ifdef PID_use_integral

template<typename T>
T PID<T>::PID_updateStatus(T status){

output_pre += this->PID_updateDelta(status);

if(output_pre>max_) output_pre = max_; //输出限幅
else if(output_pre<min_) output_pre = min_;

return output_pre;
}

#endif

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2022-2023 RY.J
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信