You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

仅支持32位float的DCS平台下PAC3200双精度电量转kWh方案问询

西门子PAC3200电表电量转换问题与解决方案

我有一台西门子Sentron PAC3200电表通过PROFIBUS连接至分布式控制系统(DCS),该电表以IEEE 754双精度格式输出以瓦时(Wh)为单位的电量计数,计数器会在1.0e12 Wh(即1000 GWh)时溢出。我的目标是以千瓦时(kWh)精度稳定记录电量,但DCS仅支持单精度浮点数(float)。若直接将双精度值转为单精度,在电量达到约100,000,000 Wh(100 MWh)时,kWh读数会出现约7位十进制数的误差,而当前电量已达600 MWh,此方案不可行。

目前我采用的方法是提取双精度值的尾数存入32位无符号双整数(UDINT),再按IEEE 754规范转换为Wh值,但该方式会在2^32 Wh(约4.3 GWh)时溢出,仅能满足约十年的需求。由于仅需kWh精度,我设想在转换初期就除以1000,这样UDINT的溢出阈值将提升至4300 GWh,超过电表自身的1000 GWh溢出值,理论上可解决问题。但IEEE 754是二进制浮点格式,仅能便捷地除以1024(右移10位),这会引入较大误差;后续在单精度下乘以1.024修正因子,又会抵消前期的努力。另一种方案是输出转换后的Wh值高位与低位UDINT,再反向计算kWh,但操作繁琐。我怀疑可能遗漏了更优方案,特寻求将传输的双精度值转换为1/1000值的可行方法。

适配EN 61131-3标准的SCL解决方案

基于@EricPostpischil的答案适配平台需求,采用结构化控制语言(SCL)编写的代码如下:

FUNCTION_BLOCK PAC3200KON_P 
VAR_INPUT 
    INH : DWORD; 
    INL : DWORD; 
END_VAR 
VAR_OUTPUT 
    OUT : UDINT; 
    SGN : BOOL; 
END_VAR 
VAR 
    significand: UDINT; 
    exponent, i, shift: INT; 
    sign: BOOL; 
    d0, d1, y0, y1, r1, temp: DWORD; 
END_VAR 

(* Convert the energy count delivered by Siemens Sentron PAC3200 (IEEE 754 binary64 format, a.k.a. double) into an UDINT. 
Peculiarities: 
- This hardware platform only supports binary32 (a.k.a. float). 
- The Sentron's internal counter overflows at 1.0e12 Wh (1000 GWh). 
- kWh resolution suffices. 
- If you converted the double directly to UDINT and divided by 1000 afterwards, the range would be reduced to (2^32-1)/1000 GWh or about 4.295 GWh. 
- This is why this function first divides the significand by 1000 and then proceeds with conversion to UDINT. 
This expands the range to (2^32-1) GWh or about 4295 GWh, which isn't reachable in practice since the device's internal counter overflows before. 

Background: IEEE 754 binary64 bit assignment: 
High-Byte Low-Byte 
66665555555555444444444433333333 
3322222222221111111111 
32109876543210987654321098765432 
10987654321098765432109876543210 
GEEEEEEEEEEESSSSSSSSSSSSSSSSSSSS 
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 
G: sign (1: negative) 
E: exponent (biased; subtract 1023) (11 bits) 
S: significand (52 bits) 
*) 

(* significand: Bits 19...0 of high byte und complete low byte 
The significand is initially divided by 1000 using integer division. 
The bits are divided into two parts: 
- d1 contains the 31 most significant bits (plus leading 1) 
- d0 contains the next less significant bits 
In total, we use 48 bits of the original significand. 
*) 

(* d1: insert significand bits from high byte *) 
d1 := INH AND 2#0000_0000_0000_1111_1111_1111_1111_1111; 
(* result: 2#0000_0000_0000_HHHH_HHHH_HHHH_HHHH_HHHH *) 

(* add the 1 before the binary point *) 
d1 := d1 OR 2#0000_0000_0001_0000_0000_0000_0000_0000; 
(* result: 2#0000_0000_0001_HHHH_HHHH_HHHH_HHHH_HHHH *) 

(* "flush left" shift 11 places *) 
d1 := d1 * 2048; 
(* result: 2#1HHH_HHHH_HHHH_HHHH_HHHH_H000_0000_0000 *) 

(* Insert another 11 bits from low byte (msb ones) *) 
d1 := d1 OR (INL / 2097152); 
(* result: 2#1HHH_HHHH_HHHH_HHHH_HHHH_HLLL_LLLL_LLLL *) 

(* Base-65536 division. Integer divide by 1000 and save remainder *) 
y1 := d1 / 1000; 
r1 := TO_DW(TO_UD(d1) MOD 1000); 

(* The significand now has leading zeroes. Shift left to make space at the other end. *) 
FOR shift := 1 TO 31 BY 1 DO 
    y1 := y1 * 2; 
    IF (y1 AND 2#1000_0000_0000_0000_0000_0000_0000_0000) <> 0 THEN 
        EXIT; 
    END_IF; 
END_FOR; 

(* d0: insert next 16 bits from the low byte (right shift five times and zero out the leading places) *) 
(* bits: 2#xxxx_xxxx_xxxL_LLLL_LLLL_LLLL_LLLx_xxxx *) 
d0 := (INL / 32) AND 2#0000_0000_0000_0000_1111_1111_1111_1111; 
(* result: 2#0000_0000_0000_0000_LLLL_LLLL_LLLL_LLLL *) 

(* Now divide by 1000, factoring in remainder from before *) 
y0 := ((r1 * 65536) OR d0) / 1000; 

(* y1 and y0 contain results from division by 1000. We'll now build a 32 bit significand from these. 
y1 = 2#1HHH_HHHH_HHHH_HHHH_HHHH_HHxx_xxxx_xxxx 
y0 = 2#0000_0000_0000_0000_LLLL_LLLL_LLLL_LLLL 
y1 has an uncertain number of zeroes at its end, resulting from the above left shifting (number of steps inside variable "shift"). 
Fill those with the most significant bits from y0. 
y0 has 16 valid bits (0..15). Shift right so that the "highest place zero" in y1 corresponds with the MSB from y0. 
(shift by 16-shift) 
y1 = 2#1HHH_HHHH_HHHH_HHHH_HHHH_HHxx_xxxx_xxxx (ex.: shift=10) 
y0 = 2#0000_0000_0000_0000_0000_00LL_LLLL_LLLL 
------->^ 
*) 
FOR i := 1 TO 16 - shift BY 1 DO 
    y0 := y0 / 2; 
END_FOR; 
significand := TO_UD(y1 OR y0); 
(* Result: 32-bit significand *) 

(* Exponent: bits (62-32)...(59-32) or bits 30...20 of high byte, respectively 
Coded with bias of 1023 (needs to be subtracted). 
Special cases as per standard: 
- 16#000: signed zero or underflow (map to zero) 
- 16#7FF: inifinite or NaN (map to overflow) 
*) 
temp := 2#0111_1111_1111_0000_0000_0000_0000_0000 AND INH; 
temp := temp / 1048576 ; (* right shift 20 places (2^20) *) 
exponent := TO_IN(TO_DI(temp)); 
exponent := exponent - 1023; (* remove bias *) 

(* Above, we already left shifted "shift" times, which needs to be taken into account here by shifting less. *) 
exponent := exponent - shift; 

(* The significand will be output as UDINT, but was initially a binary64 with binary point behind the leading 1, after which the coded exponent must be "executed". 
temp = 2#1.HHH_HHHH_HHHH_HHHH_HHHH_HLLL_LLLL_LLLL 
As UDINT, this already corresponds to a 31-fold left shift. 
Exponent cases as per IEEE 754: 
- exponent < 0: result < 1 
- exponent = 0: 1 <= result < 2 
- exponent = x > 0: 2^x <= result < 2^(x+1) 
The UDINT output (32 bit) allows us to represent exponents right up to 31. Everything above is mapped to UDINT's maximum value. 
Now determine, after the de facto 31-fold left shift, what shifts remain "to do". 
*) 
IF exponent < 0 THEN 
    (* underflow: < 2^0 *) 
    significand := 0; 
ELSIF exponent > 31 THEN 
    (* overflow: > 2^32 - 1 *) 
    significand := 4294967295; 
ELSE 
    (* result is significand * 2^exponent or here, as mentioned above, significand * 2^(31-exponent). 
    The loop index i is the "shift target" after loop execution, which is why it starts at 31-1. 
    Example: exponent = 27, but de facto we've already got a shift of 31. So we'll shift back four times to place the binary point at the right position (30, 29, 28, 27): 
    before: temp = 2#1HHH_HHHH_HHHH_HHHH_HHHH_HLLL_LLLL_LLLL. 
    after: temp = 2#1HHH_HHHH_HHHH_HHHH_HHHH_HLLL_LLLL.LLLL 
    ^<---| 
    *) 
    FOR i := 30 TO exponent BY -1 DO 
        significand := significand / 2; 
    END_FOR; 
END_IF; 

(* sign: bit 63 of high byte *) 
sign := (2#1000_0000_0000_0000_0000_0000_0000_0000 AND INH) <> 0; 
OUT := significand; 
SGN := sign; 
END_FUNCTION_BLOCK

测试数据

high bytelow bytedecimal value
16#41c558c316#2d3f331e716_277
16#41EFFFFF16#5E0000004_294_966
16#41EFFFFF16#DB0000004_294_967
16#41F0000016#2C0000004_294_968
16#426D1A9416#A1830000999_999_999
16#426D1A9416#A20000001_000_000_000
16#426D1A9416#A27D00001_000_000_001
16#428F3FFF16#FFC180004_294_967_294
16#428F3FFF16#FFE0C0004_294_967_295
16#428F400016#000000004_294_967_296

注:SCL中形如2#1234_5678的整数字面量表示二进制数字,下划线为数字分隔符,不影响数值,类似Python中的写法。


内容的提问来源于stack exchange,提问作者chr0n0ss

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:26:41