面向对象编程是一种抽象。其特质之一为封装

我们在编程的时候,常讲的模块化编程,而如何去将其模块化,就是我们在设计时的对程序的一种抽象。如果有重构的经验的同学,应该更能体会,某一天发现自己的以前的某个功能模块不满足需求,或者耦合太严重,那么就会有种重写的冲动。当然希望大家会喜欢这种感觉,因为在这个过程中,所锻炼的自己的抽象能力,以及那种把控,设计数据结构,接口是追求代码的极致。


使用c的struct写c++的class

接下来会以写一个PID控制器模块,使用封装的思想,如何用c的struct模仿c++的class的过程(或者说c++是怎么实现类的)。

希望带给大家一些新的收获

  1. 封装的思想
  2. pid的实现
  3. 函数指针
  4. class的实现


PID控制器

不介绍过多,PID控制器是经典控制,也是现在应用最多的控制器。其原理很简单,它由比例,积分,微分构成。所以也就很哲学,分别调节着系统的现在、过去、未来。

它的调节其实很容易代入理解,比如我们在洗澡的时候调节我们喜欢的温度一样,根据我们感受的温度:

  1. 比例:一点点的打开开关,
  2. 积分:开了这么多次,怎么还是冷的,根据一段时间的感受,认为应该调大点
  3. 微分:突然很烫,再开大点,肯定更烫,调小。根据这两次感受,预测未来。

如果不了解的同学可以网上多看看其他的介绍。学习总是一个过程,如果没有系统学过控制理论,仅仅使用,可以不用过于计较一些理论的东西,我们在使用的场合可能并不能,或者不好进行建模,当然去系统的学习一下最好了。

PID的离散化公式

在时域下的公式:

离散化后


在我们考虑设计一个模块的时候

  1. 模块最终的输入输出是什么
  2. 设计的接口有哪些
  3. 设计模块需要的数据结构:需要哪些变量,数组?比如队列管理?list?
  4. 如何去实现具体的功能

粗略设计PID模块

  1. 模块最终的输入输出是什么
    • 输入err,得到控制输出
  2. 设计的接口
    • 设置kp,ki,kd参数的接口
    • 设置积分限幅的接口
    • 控制器输出接口
  3. 设计模块需要的数据结构
    • 需要的变量
  4. 去实现具体的功能
    • 单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写了个简单程序是测试程序的图形
在这里插入图片描述

被抛弃的写随笔公众号改写技术文章了,感兴趣的可以关注公众号:王崇卫
在这里插入图片描述