• Not Answered

How to convert Float to 32 bit unsigned Integer on Deltav?

Hello all,

We have a communication between Omniflow and DeltaV using the special modbus driver from MYNAH, the communication is good, however I have concerns about the conversion of DataSet side DeltaV.

Modbus Packet (Addr : 001) : a packet that groups Float data and 32 bit unsignedInteger

DataSet declared on the deltaV en Float.

How to convert Float to 32 bit unsigned Integer on Deltav?

 

Modbus Packet (Addr : 201) : a packet that groups data String (ASCII-8)

The max Size on DeltaV of String Data its 96 bytes for MYNAH.

Anomalous: the DeltaV create a single register for the Data String, which gathers all the Packet of the 201.

How to ungroup the 12 string data from a single register?

 Is there a possibility to convert the 32 bit unsigned Integer to String (ASCII-8) on the DeltaV?

Regards.

10 Replies

  • About address 001: it would probably be simpler if you created two datasets in DeltaV, one for the integer and one for the float. The Omnibus should not care how many datasets you map the signals to, although I have never worked with it.
  • In reply to István Orbán:

    yes I can do like that, but the goal is to minumiser the number of dataset.
    I found a formula to do the conversion 32 Bit Unsigned Integer to Float, I post it down, I wonder if we can do the opposite (Float to 32 Bit Unsigned Integer).

    #######################
    VAR EXPONENT END_VAR;
    VAR SGN END_VAR;
    VAR MANTISSA END_VAR;

    (* IEEE754_IN is a 32 Bit Unsigned Integer parameter *)
    (* FLOAT is a floating point parameter *)

    (* Extract the Exponent from the bits 24 to 31 of the 32 bit word *)
    (* AND Mask to extract bits 24 to 31 then shift 23 bits to the right *)
    EXPONENT := SHR(('^/IEEE754_IN.CV' & 2139095040),23);

    (* Extract the Sign from the bit 32 of the 32 bit word *)
    (* AND Mask to extract bit 32 then shift 31 bits to the right *)
    SGN := SHR(('^/IEEE754_IN.CV' & 2147483648),31);

    (* Extract the Mantissa from the bits 1 to 23 of the 32 bit word *)
    (* AND Mask to extract bits 1 to 23 *)
    MANTISSA := ('^/IEEE754_IN.CV' & 8388607);


    (* The maths used to create the FLOAT from SIGN, EXPONENT and MANTISSA *)

    OUT1 := EXPONENT - 127;
    OUT2 := SGN;
    OUT3 := MANTISSA;

    '^/FLOAT.CV' := (1+ (MANTISSA * (2 ** -23))) * (2 ** OUT1);
    ##########################
  • A similar question was asked in 2012 (see here).

    I indicated a solution given in Application Exchange, which still can be used.

    Could you indicate what you want to achieve getting a 32 bit integer to string? Do you want threehundredbillionfivehundredthirtyfour for a number?

  • In reply to Maarten van der Waal:

    Thank you for answer,

    I have already tried the typical conversion, but in my case it does not work, this application is valid only if you want to make a direct conversion between correct values in Float to 2 x 16 bit.
    In my case, the data is in 32 bit uint (in Omnibus ) and it is declared Float on the DeltaV , result the value is wrong (I receive a value of 7.18667E-43 and the conversion to int gives me 0).
    The best to do as"István Orbán" said: (created two datasets in DeltaV, one for the integer and one for the float).

    For the 32 bit integer to string, if I can't ungroup the 12 string data from a single register in DeltaV, I can create a register on 2 * 32 bit integer to read the String Data but there’s no possible conversion on DeltaV.
    otherwise, if I have a String register in 96 bytes, how to divide it by 8 to get out the 12 variable.
    for exemple:
    address 201= threehundredbillionfivehundredthirtyfour
    to get:
    A= three
    B= hundred
    C= billion
    D= five ....etc.
  • In reply to Nabil BOU:

    Two quick responses to your answer:
    1.
    The float in DeltaV can handle +/- ?E+/-38 values. So a value of 7E-43 is way out of the DeltaV range.
    Can you give an example of bits and related expected floatvalue?

    2.
    DeltaV is not equipped with string handling procedures. Also no pointer variables are available. A Substring function is not available. It requires a decent parser to de-assemble the text for a number.
    Building an textstring is possible using a selfwritten assembling procedure in an action block. Strings can be build using the '+'operator.
  • In reply to Maarten van der Waal:

    Jumping late here,

    To convert a Float value to an Integer, simply write the float value (1.23457e+008) into a 32 bit unsigned integer parameter to get (123456792). Note that the resolution of a 32 bit float starts to drop off and gets rounded as the number gets bigger. In the above example, I hand entered 123456789 into the float parameter, which was stored as a float, but came back rounded to 123456792. Float number will start to loose the resolution as a Float has a mantissa of 23 bits, or 8,388,608. So if the float is equal to or less than 16777216, the integer will be the same. above this, there will be rounding in the integer. (verified this using a Float parameter and an INT32 parameter with CALC block expression:
    '^/INT32.CV' := "^/FLOAT.CV';
    No fancy math needed. As long at the Float value falls within the range of the Integer type, DeltaV will represent the value correctly, with the limitation of the float register precision.

    Going the other way, converting a 32 bit float transferred as an Integer, the integer register is read by as an integer. Since the float number is made up of its log and mantissa portions, the integer interpretation will be wrong. That's why we have to extract the parts of the float and recalculate the correct value from the integer bits.

    Converting ASCII characters to String in DeltaV

    This is much tougher. I set something up but have no idea if it is really usable. I'm wondering if this might not be done at the console using VBA. Do you need the string in the controller or for display purposes. If only at the console, you can create a function that will read the integers and convert them to the ASCII bytes, and then simply convert these to the ASCII characters in a look up function.

    If you need this string in the controller, I'm afraid that gets much harder. Here's what I've found:

    You can create a named set to convert byte to letter. This does not work for numbers and special characters as every Named State in the name set must have a letter in it. Also, the names are not case sensitive, so "A" is the same as "a". You would need to change all letters to be upper or lower case prior to storing the letter. Or, you could use "a_" for lower case, and convert to Upper case values to use "A" in the string. point is, use only upper or lower case, and convert the other accordingly. I assumed Upper case to be used and simply subtract 32 from the lower case byte.

    My test was done with two 16 bit integers representing for characters (4 bytes). My test string is "(AB)", which is 40 65 66 and 41 for byte values, and 10305 for integer 1, and 16937 for the second integer.

    I've created a Named Set parameter for each Byte that I am receiving, in this case 4. The named set is defined for a specific set of characters, not all of them. For special characters and numbers, I define the name as the name state (i.e. "ONE", "TWO", etc or "PERIOD", L Bracket, R Bracket, SPACE, etc) I decided to work of the byte value for these but it lets me store the Integer value in the parameter. Any value not defined in the named set defaults to 255, which I've defined as _|_. If I see this appear in the string, I know I'm dealing with an undefined character.

    In the module, I can see each character next to the parameters so I know they are defined in the named set.

    Unfortunately, the controller does not have a string array, so we cannot directly loop through an array to reuse code. So I'm cheating by using a While loop and adding so IF statements to direct the loop to use the right parameter.

    I'm using unsigned Integers as my input sources, but this could be 32 bit integers as well. If the value is in a Float, write it to a 32 bit integer first and then parse out the 4 bytes.

    Note********************************
    If you are using a float, and the value is greater than 16 million, the least significant byte might be in error due to rounding in the Integer to Float conversion discussed above. If passing ASCII data, you must use an Integer based data set, either 16 or 32 bit unsigned.

    here is my code for four ASCII bytes.

    '^/ASCII_STRING.CV' := ""; rem Initialise string to null and build it each scan.

    rem Extract ASCII bytes from Integer and store in Named Set Parameters

    rem Check upper byte is in lower case range and force to upper case
    If (SHR ('^/INT1.CV',8) >= 97) AND (SHR ('^/INT1.CV',8) <= 122) then
    '^/BYTE1.CVI' := SHR ('^/INT1.CV',8) -32;
    else;
    '^/BYTE1.CVI' := SHR ('^/INT1.CV',8);
    endif;
    rem check lower byte is in lower case range and force to upper case
    If (('^/INT1.CV'& 255) >= 97) AND (('^/INT1.CV'& 255) <= 122) then
    '^/BYTE2.CVI' := ('^/INT1.CV'& 255) - 32;
    else;
    '^/BYTE2.CVI' := '^/INT1.CV'& 255;
    endif;
    rem Check upper byte is in lower case range and force to upper case
    If (SHR ('^/INT2.CV',8) >= 97) AND (SHR ('^/INT2.CV',8) <= 122) then
    '^/BYTE3.CVI' := SHR ('^/INT2.CV',8) -32;
    else;
    '^/BYTE3.CVI' := SHR ('^/INT2.CV',8);
    endif;
    rem check lower byte is in lower case range and force to upper case
    If (('^/INT2.CV'& 255) >= 97) AND (('^/INT2.CV'& 255) <= 122) then
    '^/BYTE4.CVI' := ('^/INT2.CV'& 255) - 32;
    else;
    '^/BYTE4.CVI' := '^/INT2.CV'& 255;
    endif;


    REM Initialize Loop index and set exit after four passes.
    INDEX := 1;
    WHILE (INDEX <= 4) DO

    REM set internal variable to correct BYTE parameter. If there are 20 characters
    REM then you need 20 If statements here and the WHILE loop goes to 20
    IF INDEX = 1 THEN
    BYTE := '^/BYTE1.CVI';
    ENDIF;

    IF INDEX = 2 THEN
    BYTE := '^/BYTE2.CVI';
    ENDIF;

    IF INDEX = 3 THEN
    BYTE := '^/BYTE3.CVI';
    ENDIF;

    IF INDEX = 4 THEN
    BYTE := '^/BYTE4.CVI';
    ENDIF;

    REM The following is executed in each loop to build up ASCII_STRING
    rem Retrieve a value and write to ASCII_STRING parameter

    (* This is the special Characters. Note this does not use the Named Set
    state names, but appends the ASCII character directly. Numbers that are
    not defined here will not be added to the string. You need to add a state
    in the named set as well to see something other than _|_ on the individual Parameters. *)
    IF BYTE < 32 THEN
    '^/ASCII_STRING.CV' := '^/ASCII_STRING.CV' + "%";
    ENDIF;
    IF BYTE = 32 THEN;
    '^/ASCII_STRING.CV' := '^/ASCII_STRING.CV' + " ";
    ELSE
    IF BYTE = 33 THEN;
    '^/ASCII_STRING.CV' := '^/ASCII_STRING.CV' + "!";
    ELSE;
    IF BYTE = 40 THEN
    '^/ASCII_STRING.CV' := '^/ASCII_STRING.CV' + "(";
    ELSE;
    IF BYTE = 41 THEN;
    '^/ASCII_STRING.CV' := '^/ASCII_STRING.CV' + ")";
    ELSE;
    IF BYTE = 47 THEN
    '^/ASCII_STRING.CV' := '^/ASCII_STRING.CV' + ".";
    ENDIF;
    ENDIF;
    ENDIF;
    ENDIF;
    ENDIF;


    (* the following section checks if value is a number.
    I use the BYTE value and subtract 48 to get an integer from 0 to 9,
    which is added to the string *)

    REM IF VALUE WAS A NUMBER, CONCATONATE USING INTEGER
    IF (BYTE >= 48) AND (BYTE <= 57) THEN
    '^/ASCII_STRING.CV' := '^/ASCII_STRING.CV' + (BYTE - 48);
    ENDIF;

    (* Lastly, I check for letters. I have forced letters to be upper case,
    so I only check the Upper Case range. If value is a letter,
    concatenate string values from the appropriate Named Set parameter.
    Note that you need to add IF statements for each index. *)
    IF (BYTE >= 65) AND (BYTE<= 90) THEN
    IF (INDEX = 1) THEN
    '^/ASCII_STRING.CV' := '^/ASCII_STRING.CV'+ '^/BYTE1.CVS';
    ENDIF;
    IF (INDEX = 2) THEN
    '^/ASCII_STRING.CV' := '^/ASCII_STRING.CV'+ '^/BYTE2.CVS';
    ENDIF;
    IF (INDEX = 3) THEN
    '^/ASCII_STRING.CV' := '^/ASCII_STRING.CV'+ '^/BYTE3.CVS';
    ENDIF;
    IF (INDEX = 4) THEN
    '^/ASCII_STRING.CV' := '^/ASCII_STRING.CV'+ '^/BYTE4.CVS';
    ENDIF;
    ENDIF;

    REM increment index and continue processing bytes
    INDEX := INDEX +1;
    END_WHILE;

    The named set looks like this:

    ENUMERATION_SET NAME="ASCII" FIXED=F
    user="adi" time=1539627908/* "15-Oct-2018 12:25:07" */
    {
    CATEGORY="Named Sets"
    ENTRY VALUE=1 NAME="INVALID" { SELECTABLE=T }
    :
    :
    ENTRY VALUE=32 NAME="Space" { SELECTABLE=T }
    ENTRY VALUE=33 NAME="Exclamation" { SELECTABLE=T }
    :
    :
    ENTRY VALUE=40 NAME="L Bracket" { SELECTABLE=T }
    ENTRY VALUE=41 NAME="R Bracket" { SELECTABLE=T }
    :
    :
    ENTRY VALUE=47 NAME="Period" { SELECTABLE=T }

    ENTRY VALUE=48 NAME="zero" { SELECTABLE=T }
    ENTRY VALUE=49 NAME="one" { SELECTABLE=T }
    ENTRY VALUE=50 NAME="two" { SELECTABLE=T }
    :
    ENTRY VALUE=56 NAME="eight" { SELECTABLE=T }
    ENTRY VALUE=57 NAME="nine" { SELECTABLE=T }
    :
    :
    ENTRY VALUE=65 NAME="A" { SELECTABLE=T }
    ENTRY VALUE=66 NAME="B" { SELECTABLE=T }
    ENTRY VALUE=67 NAME="C" { SELECTABLE=T }
    :
    ENTRY VALUE=89 NAME="Y" { SELECTABLE=T}
    ENTRY VALUE=90 NAME="Z" { SELECTABLE=T }

    Note that the named states must be user selectable or else DeltaV inserts the NAMESET:NUMBER instead of the state name. If you see something like ascii:66 instead of the letter "A", the state is not "User Selectable".

    I hope you find this useful. One last comment. You may want to download the module with the ASCII_STRING parameter defined to a maximum size. Set this to an initializing string that will exceed the number of characters you are passing from the PLC. This will allocate sufficient memory for the string and avoid fragmentation as the string value grows over time. I believe this is only a concern for M series and older MDPlus/SDPlus controllers. But it won't hurt. Max string length is 256 so do not exceed this. Given how arduous this is, I doubt you'll want to handle 256 characters in the controller....

    Andre Dicaire

  • In reply to Andre Dicaire:

    Here is an example of my module, with lower case letters forced to Upper case, a space and a right bracket. 

    You should use a 16 bit unsigned integer data set to avoid rounding errors in a Floating point number. 

    My module shows the ASCII values on the left as inputs that create the INT1 and INT2 values, which are 16 bit.  On the right are the Named set parameters to show the character values and also to convert the byte value to a letter.

    The ASCII_STRING is the output.  It works, but is a bit tough to work with. 

    Cheers

    Andre Dicaire

  • There is also an ASCII to String Converter here, not sure if it's relevant: deltav.com/.../application.asp
  • In reply to Maarten van der Waal:

    @Maarten van der Waal

    1.
    for example: in Omniflow i have the value of 514 (32 Bit Unsigned Integer), on the DeltaV modbus i receive the value of 7,788667E-043 (Float )

  • In reply to Andre Dicaire:

    Thank you Andre, it's very interesting what you presented.
    You are right the integer interpretation will be wrong.
    For the String it's only for the graphique.
    I can do something basic for the string, that we can't ungroup the 12 string data from a single register, on the graph I create a data for string (length 96 alphabet letter) and for each variable I hide the part that does not corespend (with a rectangle or something else).