面向对象编程是一种抽象。其特质之一为封装。
我们在编程的时候,常讲的模块化编程,而如何去将其模块化,就是我们在设计时的对程序的一种抽象。如果有重构的经验的同学,应该更能体会,某一天发现自己的以前的某个功能模块不满足需求,或者耦合太严重,那么就会有种重写的冲动。当然希望大家会喜欢这种感觉,因为在这个过程中,所锻炼的自己的抽象能力,以及那种把控,设计数据结构,接口是追求代码的极致。
使用c的struct写c++的class
接下来会以写一个PID控制器模块,使用封装的思想,如何用c的struct模仿c++的class的过程(或者说c++是怎么实现类的)。
希望带给大家一些新的收获
- 封装的思想
- pid的实现
- 函数指针
- class的实现
PID控制器
不介绍过多,PID控制器是经典控制,也是现在应用最多的控制器。其原理很简单,它由比例,积分,微分构成。所以也就很哲学,分别调节着系统的现在、过去、未来。
它的调节其实很容易代入理解,比如我们在洗澡的时候调节我们喜欢的温度一样,根据我们感受的温度:
- 比例:一点点的打开开关,
- 积分:开了这么多次,怎么还是冷的,根据一段时间的感受,认为应该调大点
- 微分:突然很烫,再开大点,肯定更烫,调小。根据这两次感受,预测未来。
如果不了解的同学可以网上多看看其他的介绍。学习总是一个过程,如果没有系统学过控制理论,仅仅使用,可以不用过于计较一些理论的东西,我们在使用的场合可能并不能,或者不好进行建模,当然去系统的学习一下最好了。
PID的离散化公式
在时域下的公式:
离散化后
在我们考虑设计一个模块的时候
- 模块最终的输入输出是什么
- 设计的接口有哪些
- 设计模块需要的数据结构:需要哪些变量,数组?比如队列管理?list?
- 如何去实现具体的功能
粗略设计PID模块
- 模块最终的输入输出是什么
- 输入err,得到控制输出
- 设计的接口
- 设置kp,ki,kd参数的接口
- 设置积分限幅的接口
- 控制器输出接口
- 设计模块需要的数据结构
- 需要的变量
- 去实现具体的功能
- 单P控制器
- PI控制器
- PID控制器
我们使用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);
以下用qt写了个简单程序是测试程序的图形
被抛弃的写随笔公众号改写技术文章了,感兴趣的可以关注公众号:王崇卫
评论(0)
您还未登录,请登录后发表或查看评论