使用整数来计算PID,以提高MCU效率及减少生成的代码量
常见的PID实现方法多是基于浮点数来计算的,如果选用的MCU的Flash资源紧张,
或是想提高MCU效率,也可以使用整数来实现PID,代码如下:
typedef struct
{
shortProportion; //比例常数 Proportional Cons
shortIntegral; //积分常数 Integral Const
shortDerivative; //微分常数 Derivative Const
short LastError; //Error[-1]
short PrevError; //Error[-2]
u16SetPoint; //设定目标 Desired Value
u16SetPointBack;
u32SumError; //误差累计
} stc_PID_t;
/* 一般先调整P,再调整D,最后调整I
* P的调整要看系统的响应程度,当调整适当后,超调比较小
* D的调整是为了减少震荡,尽量少的增加,大概为P的5%
* 当震荡减小后,调整I时,对P要适当减小为80%左右,I的调整是为了减小静态误差,大约为10%的P值,不要过大
*/
void PID_Arg_Init(stc_PID_t *sPtr, u16 P, u16 I, u16 D, u16 Aims)
{
sPtr->SumError = 0;
sPtr->LastError = 0;
sPtr->PrevError = 0;
sPtr->SetPoint = Aims;
sPtr->SetPointBack = Aims;
sPtr->Proportion = P;
sPtr->Integral = I;
sPtr->Derivative = D;
}
/*******************************************************************************
* 函数名称 : IncPIDCalc
* 函数描述 : 增量式 PID 控制计算
* 函数输入 : int 当前位置
* 函数输出 : 无
* 函数返回 : 增量式PID结果
*******************************************************************************/
int16_t IncPIDCalc(stc_PID_t *sPtr, u16 NextPoint)
{
int iError, iIncpid;
// 当前误差
iError = sPtr->SetPoint - NextPoint;
// 增量计算
iIncpid = (sPtr->Proportion * iError //E项
- sPtr->Integral* sPtr->LastError //E项
+ sPtr->Derivative * sPtr->PrevError) / 32768; //E项
// 存储误差,用于下次计算
sPtr->PrevError = sPtr->LastError;
sPtr->LastError = iError;
// 返回增量值
return(iIncpid);
}
/*******************************************************************************
* 函数名称 : LocPIDCalc
* 函数描述 : 位置式 PID 控制计算
* 函数输入 : int 当前位置
* 函数输出 : 无
* 函数返回 : 位置式PID结果
*******************************************************************************/
int16_t LocPIDCalc(stc_PID_t *sPtr, u16 NextPoint)
{
intiError,dError;
iError = sPtr->SetPoint - NextPoint; // 偏差
sPtr->SumError += iError; // 积分
dError = iError - sPtr->LastError; // 微分
sPtr->LastError = iError;
return(sPtr->Proportion * iError // 比例项
+ sPtr->Integral * sPtr->SumError // 积分项
+ sPtr->Derivative * dError) / 32768; // 微分项
}
typedef struct {
int16_t Kp; // 比例增益
int16_t Ki; // 积分增益
int16_t Kd; // 微分增益
int16_t integral; // 积分累加
int16_t last_error; // 上一次误差
int16_t output_max; // 输出最大值
int16_t output_min; // 输出最小值
} PIDController;
int16_t PID_Compute(PIDController *pid, int16_t setpoint, int16_t measured_value) {
int16_t error = setpoint - measured_value; // 误差计算
int16_t proportional = pid->Kp * error; // 比例项
// 积分项(防止积分饱和)
pid->integral += pid->Ki * error;
if (pid->integral > pid->output_max) {
pid->integral = pid->output_max;
} else if (pid->integral < pid->output_min) {
pid->integral = pid->output_min;
}
// 微分项
int16_t derivative = pid->Kd * (error - pid->last_error);
// 计算输出
int16_t output = proportional + pid->integral + derivative;
// 输出限制
if (output > pid->output_max) {
output = pid->output_max;
} else if (output < pid->output_min) {
output = pid->output_min;
}
// 更新上一次误差
pid->last_error = error;
return output;
}
PID_Arg_Init(&PID_Pout, (u16)(0.5 * 32768), (u16)(0.04 * 32768), (u16)(0.02 * 32768), OutputPowerSet.OutputLimitPower * 4); // 输出功率PID调节参数初始化 可以,但要注意溢出问题。 就是ti的iqmath库的方法 如果使用整数来计算的话 会不会降低它的精度呢 其实也可以使用算法来避免强制的乘除法操作
如果出现了小数需要如何进行处理呢 计算的精度会不会收到较大的影响呢 356053261 发表于 2024-7-17 14:40
PID_Arg_Init(&PID_Pout, (u16)(0.5 * 32768), (u16)(0.04 * 32768), (u16)(0.02 * 32768), OutputPowerSet ...
直接做乘除**不会造成极大的负担呢
直接指针操作对我来说有点困难啊
这种计算出来的PID值怎么和你是实际控量结合起来的,
比如说,控制PWM输出,这个计算的这个值怎么和PWM的值对应起来的。 我都是用浮点数计算的,体会不到速度上的差别 整数代替浮点,只是对性能要求极其苛刻的条件下能用到 处理速度上,区别大不?效果怎么样? wowu 发表于 2024-7-18 21:41
直接做乘除**不会造成极大的负担呢
HC32是单周期硬件乘法器,这里用了HC32里面的16指令周期的硬件除法器,对比浮点数计算要快很多了 地瓜patch 发表于 2024-7-29 22:39
整数代替浮点,只是对性能要求极其苛刻的条件下能用到
一个是性能要求,如果芯片的flash空间很紧张,但已无法更换芯片,也可以考虑 xiaoqizi 发表于 2024-7-18 14:00
计算的精度会不会收到较大的影响呢
返回的结果为16位有符号数,精度是1/65536,足够了 sanzi666 发表于 2024-7-19 11:01
这种计算出来的PID值怎么和你是实际控量结合起来的,
比如说,控制PWM输出,这个计算的这个值怎么和PWM的值 ...
PID返回的是set_point,也就是目标值与当前值的差值,有了差值就可以计算出差值与目标值的比例,这个比例可能就是你需要调整的PWM比例,具体要看你的应用场景了 例如在数字电源系统中,PWM的脉宽输出决定了输出电压,就是把PID的返回值直接付给PWM定时器的比较寄存器CCR里面,就可以了吗 sanzi666 发表于 2024-8-2 10:58
例如在数字电源系统中,PWM的脉宽输出决定了输出电压,就是把PID的返回值直接付给PWM定时器的比较寄存器CCR ...
空载和重载的PWM脉宽是不一样的,数字电源中PID用增量PID控制,PID返回的是需要调整多少当前电压与目标电压的差值,假如你的目标电压是12V,当前电压是9V,设PID的set_point为12000,当前电压值为9000,PID返回的结果是3000,这里的3000/12000=0.25,表示还需要上调25%的电压,
再假设你的PWM周期值为1000,就用1000*0.25=250,表示当前的Duty还需要向上加250,
简化一下公式,CCR += PERIOD * IncPIDCalc(&Pid_Val, Vout) / Vout_Aim;
PERIOD是你定时器的周期值
Vout是你当前的实际电压
Vout_Aim是你的目标电压