C语言模块化设计控温器的实现
设计思路
设计一个控温器模块由加热器、制冷器、算法器三个子模块构成。控温器负责三个模块的协同工作以及与外界交换信息(设定值、实时值、控制参数等),并根据这些数据去配置或者设定三个子模块。算法器负责根据设定值、实时值的偏差计算一个输出量,并将这个输入量分别转化为加热器与制冷器的工作功率。制冷器与加热器根据算法器计算的结果,调整自己的功率输出。通过OOP的设计方法,将控温器作为一个整体封装起来,使之成为一个独立的部分(目前算法器中的部分参数仍然直接依赖外部全局变量完成初始化)。在不同的项目应用中,需要为模块编写不同的实现函数,然后通过动态或者静态的方式注册给它们,而不必考虑它们内部如何工作的、子模块之间是如何协调的。
控温器的接口设计(外部接口)
void (*Init_FP)(struct _TemperController_TypeDef *this)
: 初始化控温器。完成控温器的内部变量的初始化以及它的三个子模块的初始化。void (*SetEn_FP)(struct _TemperController_TypeDef *this, uint8_t state)
: 打开或关闭控温器。设置控温器是否处于运行状态,同时设置它的三个子模块的运行状态。uint8_t (*GetErr_FP)(struct _TemperController_TypeDef *this)
: 获取控温器错误信息。通过获取三个子模块的状态以及对控制效果的判断来设置一个错误码。void (*SetPV_FP)(struct _TemperController_TypeDef *this, float pv)
: 设置控温器的实时值。一般会在某个任务中或者定时器中断服务函数中周期性的调用。void (*SetSV_FP)(struct _TemperController_TypeDef *this, float sv)
: 设置控温器的设定值。一般是检测到SV数值发生改变时才会调用此函数。void (*Exec_FP)(struct _TemperController_TypeDef *this)
: 执行一次控温动作。一般会在某个任务中或者定时器中断服务函数中周期性的调用。
执行器的接口设计(内部接口)
加热器与制冷器都继承于执行器,通过为同样的接口注册不同的回调函数,构建了两个不同的模块,这有点类似C++中的多态特性。同时需要注意的是,执行器输入子模块,为了维持控温器的封装性,应该尽量避免在外部直接调用这些接口,同时也应该尽量避免在内部调用外部的变量与函数。
void (*Init_FP)(struct _Actuator_Typedef *this)
: 初始化执行器。void (*SetEn_FP)(struct _Actuator_Typedef *this, uint8_t state)
: 打开或关闭执行器。void (*SetPower_FP)(struct _Actuator_Typedef *this, float power)
: 设置执行器的工作功率。需要注意的是加热器多数为电流通过电阻丝发热的原理,一般支持功率可调输出,而制冷器中的半导体制冷片也是支持功率可调输出,但是压缩机形式的制冷器只存在开关两个状态(0% 或 100%)。这个差异性在实现不同模块的回调函数的时候应当予以注意。
算法器的接口设计(内部接口)
算法器是控温器的核心模块,它有着较多的参数设定与较为复杂的计算流程。控温器的输入输出主要都是与算法器交互的,然后通过算法器的计算结果去设置执行器的工作状态。
void (*Init_FP)(struct _TemperController_TypeDef *this)
: 初始化算法器。void (*SetEn_FP)(struct _TemperController_TypeDef *this, uint8_t state)
: 打开或关闭算法器。void (*SetPID_FP)(struct _Algorithm_TypeDef *this, float p, float i, float d)
: 设置算法器的PID参数。值得注意的是,算法器中还有一个PID类型的结构体,它里面也有一系列的变量与函数,设置参数主要通过调用PID中提供的修改函数实现的。同时,由于PID的参数众多,如果全部通过接口的方式完全将算法器封装起来,会造成性能的下降。因此在算法器初始化的时候,直接调用了部分全局变量完成了PID参数的初始化,这在一定程度破坏了封装性,也是对性能的妥协。void (*Update_FP)(struct _Algorithm_TypeDef *this, float sv, float pv)
: 执行一次算法器的计算。float (*GetOutput_P_FP)(struct _Algorithm_TypeDef *this)
: 获取算法器的加热器功率输出结果。float (*GetOutput_N_FP)(struct _Algorithm_TypeDef *this)
: 获取算法器的制冷器功率输出结果。
整体架构
图中是整个控温器的架构,从OOP的角度可以理解为:控温器类继承于制冷器、加热器、算法器三个父类,制冷器与加热器继承于执行器,算法器继承于PID类。为“虚函数”注册不同函数的过程,就是在构造不同的类,而声明并定义类型数组或者在堆上动态创建类型变量的过程就等同于同一个类实例化不同对象的过程。
源码实现
头文件
/*
* heater.h
*
* Created on: Jul 3, 2019
* Author: tao
*/
#ifndef HARDWARE_INC_HEATER_H_
#define HARDWARE_INC_HEATER_H_
#include "stm32f10x_conf.h"
#include "stm32f10x.h"
#ifdef HEATER
/**
* 执行器的类型定义
*/
typedef struct _Actuator_Typedef
{
uint8_t State; //0: disabled, 1: enabled
uint8_t IsRun; //0: stop, 1: run
float Pow; //Current power of actuator
void (*Init_FP)(struct _Actuator_Typedef *this); //初始化执行器
void (*SetEn_FP)(struct _Actuator_Typedef *this, uint8_t state); //0: close actuator, 1: open actuator
void (*SetPower_FP)(struct _Actuator_Typedef *this, float power); //output power of actuator
}Actuator_Typedef;
/**
* 算法器的类型定义
*/
typedef struct _Algorithm_TypeDef
{
uint8_t State; //0: disabled, 1: enabled
uint8_t IsRun;
PID_TypeDef PidData; //控制算法数据结构体指针
void (*Init_FP)(struct _Algorithm_TypeDef *this); //初始化算法器
void (*SetEn_FP)(struct _Algorithm_TypeDef *this, uint8_t state); //0: close algorithm, 1: open algorithm
void (*SetPID_FP)(struct _Algorithm_TypeDef *this, float p, float i, float d); //0: close algorithm, 1: open algorithm
void (*Update_FP)(struct _Algorithm_TypeDef *this, float sv, float pv);
float (*GetOutput_P_FP)(struct _Algorithm_TypeDef *this);
float (*GetOutput_N_FP)(struct _Algorithm_TypeDef *this);
}Algorithm_TypeDef;
typedef struct _TemperController_TypeDef
{
uint8_t State; //0: disabled, 1: enabled
uint8_t IsRun;
uint8_t Error;
float SV;
float PV;
Actuator_Typedef *Heater;
Actuator_Typedef *Cooler;
Algorithm_TypeDef *Algorithm;
void (*Init_FP)(struct _TemperController_TypeDef *this); //初始化控温器
void (*SetEn_FP)(struct _TemperController_TypeDef *this, uint8_t state); //0: close TC, 1: open TC
uint8_t (*GetErr_FP)(struct _TemperController_TypeDef *this);
void (*SetPV_FP)(struct _TemperController_TypeDef *this, float pv);
void (*SetSV_FP)(struct _TemperController_TypeDef *this, float sv);
void (*Exec_FP)(struct _TemperController_TypeDef *this);
// Void_Void_FP Init; //重置温度控制器
// Void_U8_FP SetEn; //0: 关闭控温, 1: 打开控温
// Void_Void_FP Exec; //执行控温程序
}TemperController_TypeDef;
#define TEMPER_CONTROLLER_NUM 1
#define HEATER_NUM TEMPER_CONTROLLER_NUM
#define COOLER_NUM TEMPER_CONTROLLER_NUM
#define CONTROLLER_NUM TEMPER_CONTROLLER_NUM
extern TemperController_TypeDef TemperController[TEMPER_CONTROLLER_NUM];
#endif
#endif /* HARDWARE_INC_HEATER_H_ */
源文件
/*
* heater.c
*
* Created on: Jul 3, 2019
* Author: tao
*/
#include "tc.h"
#include "user.h"
#include "mb_user.h"
#include "pid.h"
#include "virtual_pwm.h"
#ifdef HEATER
static void Heater_Init(Actuator_Typedef *this);
static void Heater_SetEn(Actuator_Typedef *this, uint8_t cmd);
static void Heater_SetPower(Actuator_Typedef *this, float power);
static void Cooler_Init(Actuator_Typedef *this);
static void Cooler_SetEn(Actuator_Typedef *this, uint8_t cmd);
static void Cooler_SetPower(Actuator_Typedef *this, float power);
static void Algorithm_Init(Algorithm_TypeDef *this);
static void Algorithm_SetEn(Algorithm_TypeDef *this, uint8_t cmd);
static void Algorithm_SetPID(Algorithm_TypeDef *this, float p, float i, float d);
static void Algorithm_Update(Algorithm_TypeDef *this, float sv, float pv);
static float Algorithm_GetOutput_P(Algorithm_TypeDef *this);
static float Algorithm_GetOutput_N(Algorithm_TypeDef *this);
static void TemperController_Init(TemperController_TypeDef *this);
static void TemperController_SetEn(TemperController_TypeDef *this, uint8_t cmd);
static uint8_t TemperController_GetErr(TemperController_TypeDef *this);
static void TemperController_SetPV(TemperController_TypeDef *this, float pv);
static void TemperController_SetSV(TemperController_TypeDef *this, float sv);
static void TemperController_Exec(TemperController_TypeDef *this);
//声明加热器、制冷器、控制器
static Actuator_Typedef HeaterArray[TEMPER_CONTROLLER_NUM] =
{
{
.State = 1,
.IsRun = 0,
.Pow = 0,
.Init_FP = Heater_Init,
.SetEn_FP = Heater_SetEn,
.SetPower_FP = Heater_SetPower,
},
};
static Actuator_Typedef CoolerArray[TEMPER_CONTROLLER_NUM] =
{
{
.State = 1,
.IsRun = 0,
.Pow = 0,
.Init_FP = Cooler_Init,
.SetEn_FP = Cooler_SetEn,
.SetPower_FP = Cooler_SetPower
},
};
static Algorithm_TypeDef AlgorithmArray[TEMPER_CONTROLLER_NUM] =
{
{
.State = 1,
.IsRun = 0,
// .SV = 30,
// .PV = 0,
.Init_FP = Algorithm_Init,
.SetEn_FP = Algorithm_SetEn,
.SetPID_FP = Algorithm_SetPID,
.Update_FP = Algorithm_Update,
.GetOutput_P_FP = Algorithm_GetOutput_P,
.GetOutput_N_FP = Algorithm_GetOutput_N,
}
};
//声明温控器
TemperController_TypeDef TemperController[TEMPER_CONTROLLER_NUM] =
{
{
.State = 1,
.IsRun = 0,
.Error = 0,
.SV = 30,
.PV = 0,
//全局变量是保存在静态存储区的,因此在编译的时候只能用常量进行初始化
//.Heater = HeaterArray[0],
//.Cooler = CoolerArray[0],
//.Algorithm = AlgorithmArray[0],
//注册函数指针
.Init_FP = TemperController_Init,
.SetEn_FP = TemperController_SetEn, //启动、关闭控温器的函数
.GetErr_FP = TemperController_GetErr,
.SetPV_FP = TemperController_SetPV,
.SetSV_FP = TemperController_SetSV,
.Exec_FP = TemperController_Exec, //周期性调用此函数,完成一次控温调整
},
};
static void Heater_Init(Actuator_Typedef *this)
{
this->State = 1;
this->IsRun = 0;
this->Pow = 0;
//打开虚拟PWM通道输出,并将占空比设置为0
VirPwm_Init(&VirPwmDefArray[0], 10, 0, 0);
VirPwm_SetStatus(&VirPwmDefArray[0], 1);
//关闭加热器开关
*HEATER_EN_ADDR = 0;
//关闭加热器风扇
*HTFAN_EN_ADDR = 0;
}
static void Heater_SetEn(Actuator_Typedef *this, uint8_t cmd)
{
if(cmd != 0)
{
this->IsRun = 1;
//打开加热器开关
*HEATER_EN_ADDR = 1;
}
else
{
this->Init_FP(this);
}
}
static void Heater_SetPower(Actuator_Typedef *this, float power)
{
//占空比设为0,即关闭该通道的PWM
//power的范围是0~1,由算法器提供
///需要注意的是power转化为对应的PWM的占空比时,要乘以一个与占空比精度相关的数,
/// 一般设置占空比精度为2,dutyCycle = power x 100;
this->Pow = power;
VirPwm_SetDutyCycle(&VirPwmDefArray[0], (uint16_t)(power*100));
//加热器功率不为0时,开启加热器风扇
if(power != 0)
{
*HTFAN_EN_ADDR = 1;
}
else
{
*HTFAN_EN_ADDR = 0;
}
}
static void Cooler_Init(Actuator_Typedef *this)
{
this->State = 1;
this->IsRun = 0;
this->Pow = 0;
*CMP_EN_ADDR = 0;
}
static void Cooler_SetEn(Actuator_Typedef *this, uint8_t cmd)
{
this->IsRun = cmd;
if(cmd != 0)
{
this->IsRun = 1;
}
else
{
this->Init_FP(this);
}
}
static void Cooler_SetPower(Actuator_Typedef *this, float power)
{
if(power != 0)
{
this->Pow = 1;
*CMP_EN_ADDR = 1;
}
else
{
this->Pow = 0;
*CMP_EN_ADDR = 0;
}
}
static void Algorithm_Init(Algorithm_TypeDef *this)
{
//初始化PID控制参数
PID_Init(&this->PidData);
///从保持寄存器中获取PID的设置参数
PID_SetPIDPara(&this->PidData, HoldingReg_GetData(11), HoldingReg_GetData(12), HoldingReg_GetData(13));
PID_SetThresPara(&this->PidData, HoldingReg_GetData(14), HoldingReg_GetData(15));
PID_SetIntegLimiting(&this->PidData, PID_DEFAULT_MAXINTEG, PID_DEFAULT_MININTEG);
PID_SetDiffLimiting(&this->PidData, PID_DEFAULT_MAXDIFF, PID_DEFAULT_MINDIFF);
}
static void Algorithm_SetEn(Algorithm_TypeDef *this, uint8_t cmd)
{
if(cmd != 0)
{
this->IsRun = 1;
}
else
{
this->IsRun = 0;
this->Init_FP(this);
}
}
static void Algorithm_SetPID(Algorithm_TypeDef *this, float p, float i, float d)
{
PID_SetPIDPara(&this->PidData, p, i, d);
}
/**
* @brief 更新一次PID计算
*/
static void Algorithm_Update(Algorithm_TypeDef *this, float sv, float pv)
{
if (this->IsRun == 0)
{
return;
}
//Get PV
// AlgorithmArray[0].PV = InputReg_GetData(1);
//Get SV
// AlgorithmArray[0].SV = HoldingReg_GetData(8);
//Set PID
// PID_SetPara(&AlgorithmArray[0].PidData, HoldingReg_GetData(11), HoldingReg_GetData(12), HoldingReg_GetData(13));
//Set PV
PID_Update(&this->PidData, sv, pv);
}
/**
* @brief (根据PID计算值)计算加热器输出功率
* 注意此函数依赖了两个全局变量HR_Ht_PID_UT与HR_Ht_PID_DT,
* 这两个变量由于一般程序中无需修改,因此没有设置专用的接口函数
* @return 加热器输出功率
*/
static float Algorithm_GetOutput_P(Algorithm_TypeDef *this)
{
// static int try_count = 0;
if (this->IsRun == 0)
{
return 0;
}
return PID_GetOutput(&this->PidData);
}
/**
* @brief (根据PID计算值)计算制冷器输出功率
* 注意此函数依赖了两个全局变量HR_Cmp_UT与HR_Cmp_DT,
* 这两个变量由于一般程序中无需修改,因此没有设置专用的接口函数
* @return 制冷器输出功率
*/
static float Algorithm_GetOutput_N(Algorithm_TypeDef *this)
{
// static int try_count = 0;
if (this->IsRun == 0)
{
return 0;
}
// float pidOut = PID_GetOutput(&AlgorithmArray[0].PidData);
float error = PID_GetError(&this->PidData);
//如果PV超出SV的温度达到HR_Cmp_UT,启动压缩机
if (error < 0 - HoldingReg_GetData(16))
{
return 1;
}
return 0;
}
/**
* @brief 初始化控温器
*/
static void TemperController_Init(TemperController_TypeDef *this)
{
this->Heater = &HeaterArray[0];
this->Cooler = &CoolerArray[0];
this->Algorithm = &AlgorithmArray[0];
this->Heater->Init_FP(this->Heater);
this->Cooler->Init_FP(this->Cooler);
this->Algorithm->Init_FP(this->Algorithm);
}
/**
* @brief 设置控温器状态(打开或关闭)
* @param cmd: 0 关闭控温器, 1 打开控温器
*/
static void TemperController_SetEn(TemperController_TypeDef *this, uint8_t cmd)
{
//开启控温,开启控制器、加热器、制冷器
if(cmd != 0)
{
this->IsRun = 1;
this->Algorithm->SetEn_FP(this->Algorithm, 1);
this->Heater->SetEn_FP(this->Heater, 1);
this->Cooler->SetEn_FP(this->Cooler, 1);
}
//关闭控温,关闭控制器、加热器、制冷器
else
{
this->IsRun = 0;
this->Algorithm->SetEn_FP(this->Algorithm, 0);
this->Heater->SetEn_FP(this->Heater, 0);
this->Cooler->SetEn_FP(this->Cooler, 0);
}
}
uint8_t TemperController_GetErr(TemperController_TypeDef *this)
{
return this->Error;
}
void TemperController_SetPV(TemperController_TypeDef *this, float pv)
{
this->PV = pv;
}
void TemperController_SetSV(TemperController_TypeDef *this, float sv)
{
this->SV = sv;
}
/**
* @brief 执行一次控温调整(仅在控温仪打开的时候,才能调用)
* 一般在定时器的更新中断中周期性的调用此函数,每调用一次就执行一次控温调整。
* 注意此函中依赖了与PID参数相关的三个全局变量P, I , D。
*/
static void TemperController_Exec(TemperController_TypeDef *this)
{
static float pValue = -1;
static float iValue = -1;
static float dValue = -1;
//控制参数发生变化则重新修改算法的PID参数
if((pValue != HoldingReg_GetData(11)) || (iValue != HoldingReg_GetData(12)) || (dValue != HoldingReg_GetData(13)))
{
pValue = HoldingReg_GetData(11);
iValue = HoldingReg_GetData(12);
dValue = HoldingReg_GetData(13);
///将PID参数的设置也加入到每次动作调整轮询中,这样可以实现参数设定后的动态调整。
///PID启动区间阈值与积分、微分上下限仅在初始化算法器的时候写入。
this->Algorithm->SetPID_FP(this->Algorithm, pValue, iValue, dValue);
}
this->Algorithm->Update_FP(this->Algorithm, this->SV,this->PV);
this->Heater->SetPower_FP(this->Heater, this->Algorithm->GetOutput_P_FP(this->Algorithm));
this->Cooler->SetPower_FP(this->Cooler, this->Algorithm->GetOutput_N_FP(this->Algorithm));
/*后面加上出错的判断,将Error改为对应的错误码值*/
}
#endif
使用指南
控温器在一定程度上实现了封装,但它在整体上就像一个逻辑框架。在实际使用的时候,需要为各个模块编写不同的实现函数,然后通过动态或者静态的方式注册给控温器、加热器、制冷器、算法器,而不必考虑它们内部如何工作的、子模块之间是如何协调的。一般情况下,控温器的实现函数主要是在生成错误码的逻辑上需要修改,加热器与制冷器则是在硬件相关的底层上需要修改(例如不同的PWM产生方式、不同的GPIO控制端口等),算法器主要是计算加热器与制冷器工作功率的地方需要修改。
控温器构建完成后,外部使用它相对会简单很多:
- 在程序开始的时候,初始化控温器
TemperController[0].Init_FP(&TemperController[0]);
(注意需要手动传入this指针) - 在需要打开控温器的地方开启控温器
TemperController[0].SetEn_FP(&TemperController[0],1);
在使用了modbus通讯协议的情况下,我们一般会使用一个线圈寄存器作为打开或者关闭控温器的信号:
static uint8_t coilsReg_buffer[32];
//开启温度控制
if(coilsReg_buffer[1] != CoilsReg_GetBit(1))
{
coilsReg_buffer[1] = CoilsReg_GetBit(1);
//打开控温器
TemperController[0].SetEn_FP(&TemperController[0], coilsReg_buffer[1]);
}
- 在定时器的更新中断服务函数或者实时操作系统的一个任务中周期性的调用
TemperController[0].SetPV_FP(&TemperController[0], SenPt100Array[0].CalibValue)
与TemperController[0].Exec_FP(&TemperController[0]);
一般情况下,我们需要先判断控温器的状态与设定值是否变化,然后才会调用控温器的控温调整执行函数:
static float heaterSV = 0;
if(TemperController[0].IsRun != 0)
{
//SV发生改变的时候才对控温器进行设定
if(heaterSV != HoldingReg_GetData(8))
{
heaterSV = HoldingReg_GetData(8);
TemperController[0].SetSV_FP(&TemperController[0], heaterSV);
}
//实时刷新PV
TemperController[0].SetPV_FP(&TemperController[0], SenPt100Array[0].CalibValue);
TemperController[0].Exec_FP(&TemperController[0]);
}