面向对象编程是一种抽象。其特质之一为封装。
我们在编程的时候,常讲的模块化编程,而如何去将其模块化,就是我们在设计时的对程序的一种抽象。如果有重构的经验的同学,应该更能体会,某一天发现自己的以前的某个功能模块不满足需求,或者耦合太严重,那么就会有种重写的冲动。当然希望大家会喜欢这种感觉,因为在这个过程中,所锻炼的自己的抽象能力,以及那种把控,设计数据结构,接口是追求代码的极致。
接下来会以写一个PID控制器模块,使用封装的思想,如何用c的struct模仿c++的class的过程(或者说c++是怎么实现类的)。
希望带给大家一些新的收获
不介绍过多,PID控制器是经典控制,也是现在应用最多的控制器。其原理很简单,它由比例,积分,微分构成。所以也就很哲学,分别调节着系统的现在、过去、未来。
它的调节其实很容易代入理解,比如我们在洗澡的时候调节我们喜欢的温度一样,根据我们感受的温度:
如果不了解的同学可以网上多看看其他的介绍。学习总是一个过程,如果没有系统学过控制理论,仅仅使用,可以不用过于计较一些理论的东西,我们在使用的场合可能并不能,或者不好进行建模,当然去系统的学习一下最好了。
在时域下的公式:
u ( x ) = K p ( e ( t ) + 1 T i ∫ 0 t e ( t ) d t + T d d e ( t ) d t ) u\left( x \right) =K_p\left( e\left( t \right) +\frac{1}{T_i}\int_0^t{e\left( t \right)}dt+T_d\frac{de\left( t \right)}{dt} \right) u(x)=Kp(e(t)+Ti1∫0te(t)dt+Tddtde(t))
离散化后
u ( k ) = K p e ( k ) + K p T T i ∑ n = 0 k e ( n ) + K p T d T ( e ( k ) − e ( k − 1 ) ) u\left( k \right) =K_pe\left( k \right) +\frac{K_pT}{T_i}\sum_{n=0}^k{e\left( n \right)}+\frac{K_pT_d}{T}\left( e\left( k \right) -e\left( k-1 \right) \right) u(k)=Kpe(k)+TiKpTn=0∑ke(n)+TKpTd(e(k)−e(k−1))
u ( k ) = K p e ( k ) + K i ∑ n = 0 k e ( n ) + K d ( e ( k ) − e ( k − 1 ) ) u\left( k \right) =K_pe\left( k \right) +K_i\sum_{n=0}^k{e\left( n \right)}+K_d\left( e\left( k \right) -e\left( k-1 \right) \right) u(k)=Kpe(k)+Kin=0∑ke(n)+Kd(e(k)−e(k−1))
我们使用struct仿class的.h文件:
/***************************************************************
* @Copyright(C) 2020-2021, wangchongwei
* @FileName: pid.h
* @Author: wangchongwei
* @Version: 0.1.0
* @LastDate: 2021.7.31
************************************************************/
#ifndef _PID_H_
#define _PID_H_
#ifdef __cplusplus
extern "C" {
#endif
struct PID
{
/* set kp ki kd */
void (* SetCtrlPrm)(struct PID *self, float kp, float ki, float kd);
/* set integral range */
void (* SetIntegralPrm)(struct PID *self, float integral_up, float integral_low);
/* Controller interface */
float (* P)(struct PID *self, float err);
float (* PI)(struct PID *self, float err);
float (* PID)(struct PID *self, float err);
/* in value */
float err;
/* out value */
float out;
/* private */
struct
{
float kp;
float ki;
float kd;
float integral_up;
float integral_low;
float sum;
float pre_err;
}pri;
};
// Object construction
void PID_Constructor(struct PID *self);
#ifdef __cplusplus
}
#endif
#endif /*_FPID_H_*/
.c文件如下
/***************************************************************
* @Copyright(C) 2020-2021, wangchongwei
* @FileName: pid.c
* @Author: wangchongwei
* @Version: 0.1.0
* @LastDate: 2021.7.31
************************************************************/
#include "pid.h"
// 设置控制参数
static void _SetCtrPrm(struct PID *self, float kp, float ki, float kd)
{
self->pri.kp = kp;
self->pri.ki = ki;
self->pri.kd = kd;
}
// 设置积分限幅
static void _SetIntegralPrm(struct PID *self, float integral_up, float integral_low)
{
self->pri.integral_up = integral_up;
self->pri.integral_low = integral_low;
}
// 比例控制器
static float _P(struct PID *self, float err)
{
self->err = err;
self->out = self->err * self->pri.kp;
return self->out;
}
// 比例积分控制器
static float _PI(struct PID *self, float err)
{
self->err = err;
// kp*err+ki*err_sum
self->out = self->pri.kp * self->err + self->pri.ki * self->pri.sum;
// 误差积分
self->pri.sum += self->err;
// 积分限幅
if (self->pri.sum > self->pri.integral_up) self->pri.sum = self->pri.integral_up;
if (self->pri.sum < self->pri.integral_low) self->pri.sum = self->pri.integral_low;
return self->out;
}
// PID控制器
static float _PID(struct PID *self, float err)
{
self->err = err;
// kp*err+ki*err_sum + kd*(err - last_err)
self->out = self->pri.kp * self->err +
self->pri.ki * self->pri.sum +
self->pri.kd*(self->err - self->pri.pre_err);
// 误差积分
self->pri.sum += self->err;
// 积分限幅
if (self->pri.sum > self->pri.integral_up) self->pri.sum = self->pri.integral_up;
if (self->pri.sum < self->pri.integral_low) self->pri.sum = self->pri.integral_low;
// 记录上次误差
self->pri.pre_err = self->err;
return self->out;
}
// 构造函数将接口绑定
void PID_Constructor(struct PID *self)
{
self->SetCtrlPrm = _SetCtrPrm;
self->SetIntegralPrm = _SetIntegralPrm;
self->P = _P;
self->PI = _PI;
self->PID = _PID;
}
如何使用:
// 声明pid类
struct PID pid;
// 构造pid
PID_Constructor(&pid);
// 设置相关变量
pid.SetCtrlPrm(&pid, 1.3, 0.005, 0.1);
pid.SetIntegralPrm(&pid,10000,-10000);
// 调用
out = pid.PID(&pid,err);
被抛弃的写随笔公众号改写技术文章了,感兴趣的可以关注公众号:王崇卫