FUNCTION "FLUXbase" : VOID
VAR_INPUT
PV : REAL ; //瞬时流量
CYC : REAL ; //采样时间(MS)
PLL : REAL ; //最小瞬时流量
END_VAR
VAR_IN_OUT
OV_ER1 : REAL ; //累计偏差
OV_ER2 : REAL ; //累计偏差
PV_ALT : REAL ; //上周期瞬时流量
OV : REAL ; //流量累积输出
END_VAR
VAR_TEMP
tmpReal1 : REAL ;
tmpReal2 :REAL ;
OValt : REAL ;
END_VAR
BEGIN
OValt := OV ;
IF PV >= PLL THEN
tmpReal1 := PV * (CYC / 3.6E+6 ) + OV_ER1 ;
tmpReal2 := tmpReal1 + OV_ER2 ;
OV := tmpReal2 + OValt ;
OV_ER1 := OV_ER2 - tmpReal2 + tmpReal1 ;
OV_ER2 := OValt - OV + tmpReal2 ;
ELSE
OV := OValt ;
END_IF ;
IF OV >= 1.000000e+009 THEN
OV := 0.000000e+000;
END_IF ;
END_FUNCTION
这是我的累积计算,程序简单,精度也比较高
OValt = 1.0e+006
tmpReal2 = 0.1
OV = OValt + tmpReal2 = 1.0e+006 + 0.1 = 1.0e+006
OV_ER2 := OValt - OV + tmp Real2 = 1.0e+006 - 1.0e+006 + 0.1 = 0.1
qlhcco
2008-5-13 18:36:36
我把我的家底都抖给你了,这是浮点数在执行加减法时产生的误差,我把每次运算的误差再加的本次的积分里,不管再小的数,累加定量以后,他就可以加到OV里了,所以我的这个算法不会因为OV值大了而不能累积。
你可以模拟一下,有偏差变量和没有偏差变量的区别。你会发现其中的奥妙了。
yzdatou
2008-5-14 7:11:04
模拟了一下确实是对的。
谢谢ls指教。
你的思路很独特,受教了。
qlhcco
2008-5-14 9:56:14
我这个思路也不是完全是我自己的,其实是我在研究SETP7 FB41的PID功能是发现的,最后我就应用在累积计算的算法里了
yzdatou
2008-5-14 19:20:34
很佩服你的钻研精神,自愧弗如。
另外,我思考再三,仍然要给你来个鸡蛋里面挑骨头,呵呵。
假定累积量越来越大,最终达到8位数,而实际有效数字仅7位,那么这个累计量的个位数将不再变化了。
qlhcco
2008-5-15 0:07:57
确实是这样的,这是由于浮点数的问题,这个问题不是不可以解决,dint型数据可以显示2147483647;可以将OV结果转换为DINT类型数据,OV_ER2也转换成DINT类型数据,OV(dint)+ OV_ER2( dint )= OUT(显示用)该输出不用于计算只是用于显示。你认为如何?
yzdatou
2008-5-15 16:26:57
你说的没错。我就是用dint类型变量来存储累积量,再用一个real变量存储小于1的累积量到1就进位。
FUNCTION_BLOCK FB618
//
TITLE = 'LEIJI'
//
know_how_protect
VERSION: '6.0'
AUTHOR: nyj
NAME: nyj
FAMILY: china
{S7_m_c:='true'}
// Block Parameters
VAR_INPUT
// Input Parameters
TongDaoDiZhi{S7_m_c:='true'}:WORD:=0;// AI adress
XunHuanShiJian{S7_m_c:='true'}:REAL:=100;// unit:ms
Liangcheng{S7_m_c:='true'}:REAL:=0.0;// m3/h(max) , t/h(max)
QingLing{S7_m_c:='true';S7_string_0:='false';S7_string_1:='true'}:BOOL:=0;// back to zero
END_VAR
VAR_OUTPUT
// Output Parameters
ShunShiZhi{S7_m_c:='true'}:REAL;//m3/h , t/h
LeiJiZhi{S7_m_c:='true'}:DINT;//m3 , t
END_VAR
VAR_IN_OUT
LeiJiHH:DINT:=0;//(1e--??ee)
LeiJiH:DINT:=0;//(1--1e)
LeiJiL:REAL:=0.0;//(0.0--1.0)
END_VAR
BEGIN
IF WORD_TO_INT(TongDaoDiZhi)< 0 OR WORD_TO_INT(TongDaoDiZhi)= 32767 OR WORD_TO_INT(TongDaoDiZhi)= -32767 THEN
TongDaoDiZhi:=0;
END_IF;
IF WORD_TO_INT(TongDaoDiZhi)> 27648 AND WORD_TO_INT(TongDaoDiZhi)<> 32767 AND WORD_TO_INT(TongDaoDiZhi)<> -32767 THEN
TongDaoDiZhi:=27648;
END_IF;
ShunShiZhi:=WORD_TO_INT(TongDaoDiZhi)*Liangcheng/27648.0;
LeiJiL:=LeiJiL+ShunShiZhi*XunHuanShiJian/3600000.0;
IF LeiJiL >= 1.0 AND LeiJiL < 100000000.0 THEN
IF REAL_TO_DINT(LeiJiL)<=LeiJiL THEN
LeiJiH:= LeiJiH + REAL_TO_DINT(LeiJiL / 1.0);
LeiJiL:= LeiJiL- REAL_TO_DINT(LeiJiL / 1.0);
ELSE
LeiJiH:= LeiJiH + REAL_TO_DINT(LeiJiL / 1.0)-1;
LeiJiL:= LeiJiL - REAL_TO_DINT(LeiJiL / 1.0)+1;
END_IF;
END_IF;
IF LeiJiL >= 100000000.0 THEN
IF REAL_TO_DINT(LeiJiL/100000000.0)<=(LeiJiL/100000000.0) THEN
LeiJiHH:= LeiJiHH + REAL_TO_DINT(LeiJiL / 100000000.0);
LeiJiL:= LeiJiL- REAL_TO_DINT(LeiJiL / 100000000.0)*100000000.0;
ELSE
LeiJiHH:= LeiJiHH + REAL_TO_DINT(LeiJiL / 100000000.0)-1;
LeiJiL:= LeiJiL - (REAL_TO_DINT(LeiJiL / 100000000.0)-1.0)*100000000.0;
END_IF;
END_IF;
IF LeiJiH >= 100000000.0 THEN
LeiJiHH:= LeiJiHH+(LeiJiH DIV 100000000);
LeiJiH:= LeiJiH-(LeiJiH DIV 100000000)*100000000;
END_IF;
IF LeiJiL < 0.0 THEN
LeiJiL:=0.0;
END_IF;
IF LeiJiH < 0 THEN
LeiJiH:=0;
END_IF;
IF LeiJiHH < 0 THEN
LeiJiHH:=0;
END_IF;
LeiJiZhi:=REAL_TO_DINT(LeiJiHH*100000000.0+LeiJiH+LeiJiL);
IF QingLing = true THEN
QingLing := false ;
LeiJiHH:= 0 ;
LeiJiH:= 0 ;
LeiJiL:= 0.0 ;
END_IF;
END_FUNCTION_BLOCK
―――――――――――――――――――――――――――――――――
粗粗看了下,程序先进行量程转换,得出瞬时流量,然后进行累加。在累加的时候,考虑到运算精度问题,也就是两个real数据运算的误差问题,所以作者考虑的很周到,采用低位累加再向高位进位的办法。其实我采用的思路跟作者类似但是更简单:直接把瞬时量分解为整数和小数部分,整数部分进行累加自然不存在精度问题,而小数部分进行累积满1以后再进位给整数部分,小数部分的运算会存在一点点误差但是基本可以忽略,这样程序要简单的多。
另外量程转换也没有必要写在这个模块里面,而是应当统一进行转换,程序的模块化是个仁者见仁智者见智的问题,这是题外话了。