This repository has been archived on 2025-02-28. You can view files and clone it, but cannot push or open issues or pull requests.
controller-hd/User/lib/control/src/pid_auto_tune.c

231 lines
9.5 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "pid_auto_tune.h"
#include "sys.h"
/*
设置峰值回溯时间,单位 0.1 秒,最小 0.2秒, 最大 4 秒
*/
static void set_look_backsec(pid_auto_tune_t *self, int32_t value)
{
if (value < 2)
value = 2;
if (value > 40)
value = 40;
if (value < 40)
{
self->pri.nLookBack = 12; // 按目前实际周期约300ms、采样周期 10ms 考虑,一个周期只有 30 点,回溯 12 点即可。
self->pri.sampleTime = value * 10; // 改为 Value*10 ms 20、30、40 ~ 200ms
}
else
{
self->pri.nLookBack = 50 + value;
self->pri.sampleTime = 4000;
}
}
static void _set_ctrl_prm(struct PID_AUTO_TUNE *self, float32 *input, float32 *output)
{
self->pri.input = input;
self->pri.output = output;
self->pri.controlType = 0; // 默认为 PI 模式
self->pri.noiseBand = 1;
self->pri.running = FALSE;
self->pri.oStep = 1;
set_look_backsec(self, 8);
self->pri.lastTime = sys_millis();
}
static void _set_noise_band(struct PID_AUTO_TUNE *self, int32_t value)
{
self->pri.noiseBand = value;
}
static void _set_output_step(struct PID_AUTO_TUNE *self, int32_t value)
{
self->pri.oStep = value;
}
// * Determies if the tuning parameters returned will be PI (D=0)
// or PID. (0=PI, 1=PID)
static void _set_control_type(struct PID_AUTO_TUNE *self, int32_t value)
{
self->pri.controlType = value;
}
static void _set_look_back(struct PID_AUTO_TUNE *self, int32_t value)
{
set_look_backsec(self, value);
}
static float32 _get_kp(struct PID_AUTO_TUNE *self)
{
float32 kp = self->pri.controlType == 1 ? 0.6f * self->pri.Ku : 0.4f * self->pri.Ku;
return kp;
}
static float32 _get_ki(struct PID_AUTO_TUNE *self)
{
float32 ki = self->pri.controlType == 1 ? 1.2f * self->pri.Ku / self->pri.Pu : 0.48f * self->pri.Ku / self->pri.Pu;
return ki;
}
static float32 _get_kd(struct PID_AUTO_TUNE *self)
{
return self->pri.controlType == 1 ? 0.075f * self->pri.Ku * self->pri.Pu : 0;
}
/**
* @brief 修改返回值0 - 执行计算,未完成整定, 1 - 执行计算,完成整定过程, 2 - 采样时间未到
* @return {*}
*/
static int32_t _runtime(struct PID_AUTO_TUNE *self)
{
int32_t i, iSum;
uint32_t now = sys_millis();
if ((now - self->pri.lastTime) < ((uint32_t)self->pri.sampleTime))
{
return 2; // 原来返回值为 FALSE 不符合函数定义,也无法区分,改为 2by shenghao.xu
}
// 开始整定计算
self->pri.lastTime = now;
float32 refVal = *(self->pri.input);
if (FALSE == self->pri.running) // 首次进入,初始化参数
{
self->pri.peakType = 0;
self->pri.peakCount = 0;
self->pri.peakMaxCount = 0;
self->pri.peak1 = 0;
self->pri.peak2 = 0;
self->pri.justchanged = FALSE;
self->pri.setpoint = refVal; // 不变
self->pri.running = TRUE;
self->pri.outputStart = *self->pri.output;
*self->pri.output = self->pri.outputStart + self->pri.oStep;
}
// 根据输入与设定点的关系振荡输出
if (refVal > (self->pri.setpoint + self->pri.noiseBand))
*self->pri.output = self->pri.outputStart - self->pri.oStep;
else if (refVal < (self->pri.setpoint - self->pri.noiseBand))
*self->pri.output = self->pri.outputStart + self->pri.oStep;
// bool isMax=TRUE, isMin=TRUE;
self->pri.isMax = TRUE;
self->pri.isMin = TRUE;
// id peaks
/*
以下循环完成,对回溯次数的输入缓存进行判断,如果输入值均大于或小于缓存值,则确定此次为峰值。
峰值特征根据 isMax、isMin 哪个为真确定。
同时完成输入缓存向后平移,腾出第一个单元存放新的输入值。
这一段代码完成的噪声所产生的虚假峰值判断,应该没有问题!
*/
for (i = self->pri.nLookBack - 1; i >= 0; i--)
{
int32_t val = self->pri.lastInputs[i];
if (self->pri.isMax)
self->pri.isMax = (refVal > val); // 第一次是新输入和缓存最后一个值比较,如果大于,则前面的值均判是否大于
if (self->pri.isMin)
self->pri.isMin = (refVal < val); // 第一次是新输入和缓存最后一个值比较,如果小于,则前面的值均判是否小于
self->pri.lastInputs[i + 1] = self->pri.lastInputs[i]; // 每采样一次,将输入缓存的数据向后挪一次
}
self->pri.lastInputs[0] = refVal; // 新采样的数据放置缓存第一个单元。
/*
以下代码完成峰值的确定,以及对应峰值的时间纪录。
因为上述代码只是去掉噪产生的波动峰值,但如果是连续超过 nLookBack 次数的的上升或下降,
则上述算法所确定的最大或最小值,并非是峰值,只能是前 nLookBack 次中的最大或最小值。
但逐句消化程序后,发现这段处理有几点疑惑:
1、peaks[] 的纪录好像不对在执行最小到最大值转换时peakCount 也应该+1否则应该把
纪录的最小值覆盖了!所以后面的峰值判断总是满足条件。
2、峰值对应时间似乎也应该多次存放取平均值因对象没有那么理想化目前应该是取的最后一组峰值的周期。
3、后续计算 Ku 用的是整个整定过程的最大、最小值,这对于非理想的对象而言也不是很合适。
考虑做如下改进:
1修改峰值纪录设计12个峰值保存单元存满12个峰值6大、6小后再计算。
2纪录 6 组最大值的间隔时间,作为最终计算 Pu 的数据。
*/
if (self->pri.isMax)
{
if (self->pri.peakType == 0)
self->pri.peakType = 1; // 首次最大值,初始化
if (self->pri.peakType == -1) // 如果前一次为最小值,则标识目前进入最大值判断
{
self->pri.peakType = 1; // 开始最大值判断
self->pri.peakCount++; // 峰值计数 by shenghao.xu
self->pri.justchanged = TRUE; // 标识峰值转换
if (self->pri.peak2 != 0) // 已经纪录一次最大峰值对应时间后,开始记录峰值周期 by shenghao.xu
{
self->pri.peakPeriod[self->pri.peakMaxCount] = (int32_t)(self->pri.peak1 - self->pri.peak2); // 最大峰值间隔时间(即峰值周期)
self->pri.peakMaxCount++; // 最大峰值计数
}
self->pri.peak2 = self->pri.peak1; // 刷新上次最大值对应时间
}
self->pri.peak1 = now; // 保存最大值对应时间 peak1
self->pri.peaks[self->pri.peakCount] = refVal; // 保存最大值
} // 此段代码可以保证得到的是真正的最大值因为peakType不变则会不断刷新最大值
else if (self->pri.isMin)
{
if (self->pri.peakType == 0)
self->pri.peakType = -1; // 首次最小值,初始化
if (self->pri.peakType == 1) // 如果前一次是最大值判断,则转入最小值判断
{
self->pri.peakType = -1; // 开始最小值判断
self->pri.peakCount++; // 峰值计数
self->pri.justchanged = TRUE;
}
if (self->pri.peakCount < 10)
self->pri.peaks[self->pri.peakCount] = refVal; // 只要类型不变,就不断刷新最小值
}
/* by shenghao.xu
以下计算是作为判断采集数据是否合适的部分,如果 2 次峰值判断条件满足,就结束整定过程,感觉不甚合理。
拟修改为:
1计满 12 次峰值后再计算(到第 13 次)。
2不再判断是否合理因为对象如果特性好自然已经稳定如果不好再长时间也无效果。
3将后面5次的数据作为素材去掉第一组数据因为考虑第一组时对象可能处于过渡过程。
4用后 10 点得到的 9 个峰值差平均值作为 Ku 计算值中的 A取代原来的整个过程的最大、最小值差。
5用后 5 点峰值周期平均值作为 Pu 的计算值,取代原来用最后一组的值。
*/
if (self->pri.justchanged && self->pri.peakCount == 12)
{
// we've transitioned. check if we can autotune based on the last peaks
iSum = 0;
for (i = 2; i <= 10; i++)
iSum += ABS(self->pri.peaks[i] - self->pri.peaks[i + 1]);
iSum /= 9; // 取 9 次峰峰值平均值
self->pri.Ku = (float32)(4 * (2 * self->pri.oStep)) / (iSum * 3.14159); // 用峰峰平均值计算 Ku
iSum = 0;
for (i = 1; i <= 5; i++)
iSum += self->pri.peakPeriod[i];
iSum /= 5; // 计算峰值的所有周期平均值
self->pri.Pu = (float32)(iSum) / 1000; // 用周期平均值作为 Pu单位
*self->pri.output = 0;
self->pri.running = FALSE;
return 1;
}
self->pri.justchanged = FALSE;
return 0;
}
void pid_auto_tune_constructor(struct PID_AUTO_TUNE *self)
{
self->set_ctrl_prm = _set_ctrl_prm;
self->runtime = _runtime;
self->set_output_step = _set_output_step;
self->set_control_type = _set_control_type;
self->set_noise_band = _set_noise_band;
self->set_look_back = _set_look_back;
self->get_kp = _get_kp;
self->get_ki = _get_ki;
self->get_kd = _get_kd;
}