« HOWTO: Concatenate or Merge PDF Documents | Vista's Windows Mail is not a good replacement for Outlook Express » |

11:49:33 pm by guy, Categories: Software Development, Windows, Random Stuff , Tags: allen-bradley, ieee, plc, vb6

I was working on a modification to an old VB6 project today and I ran across some code that I wrote over 6 years ago that brought back some memories. The software development that I do is primarily with manufacturing related applications. I work in a manufacturing facility with a lot of automation. Many of the applications that I work on require communication with PLCs (Programmable Logic Controllers). The problem that I was solving here was related to the way that data was being stored in some of the older model PLCs. Newer generation PLCs that we use like the Allen-Bradley (Rockwell Automation) ControlLogix processors support pretty advanced data structures, but the older processors that I have worked with such as the SLC500, PLC5, and PLC5/250 have what I would describe as much more primitive (or at least rigid) data structures.

On these older processors, blocks of memory, or files as we call them were limited to a single data type. When communicating with these processors you could do very effiicient reads of data, but generally only one file at a time. As a result, when we create queues of data to be transferred between the PLC and a PC application (generally to collect product traceability data such as component lots, serial numbers, and test data) we would be limited to a single data type. An example would be that as parts come off the line we would have 10 integer files (say N100-N109) each with 100 16bit words of data that represent a queue of up to 10 parts. The type of data that needs to be stored in these files includes Integer, String, and Floating point values. The ASCII or string data is typically stored as 2 characters / 16bit word. If anyone is interested in that translation code let me know and I’ll follow up with an example. The integer data is straightforward, but of course limited to 16 bit values up to 32768. The floating point values are the tricky ones.

Until I wrote this code it was all black magic to me, how do you have a floating point value in an integer location? Now I look back and realize how little I understood about this stuff! Back then, the Controls Engineers (they write the PLC code) would say that a value was IEEE. I’d say "huh?" and ask how I could see the actual value. They would explain that you can’t, making it sound really complicated. Well, even now, I think it is complicated, but some smart people figured this stuff out a long time ago. Eventually I HAD to understand it so that I could retrieve some of these values.

The quick explaination is that IEEE encoding is how a computer represents values internally. Ultimately, everything is bits which can represent integer values very well, but floating point values are more complex to represent. In the PLC you can see the IEEE integer values if you directly copy the floating point memory into an integer file. It is just a bit by bit copy of the floating point words into integer files. By using this technique we can transfer floating point values efficiently along with the integer and ASCII data in very efficient reads (or writes). On the PC side though, we need a function to convert the integer values back to something more meaningful to us humans. That is what this code does.

The VB6 code below is the code that I came up with. I think it is commented enough to understand without too much extra comment here. One item to note is that some architechtures (I think you’ll see this in the comments) require that the words be swapped and others don’t. I believe that the 5/250 didn’t but all the other old AB processors do.

If this helps you please leave a comment. I curious if I have reinvented the wheel or if this is helpful to someone out there.

Be sure to compare my final version with the commented section at the bottom that shows how difficult I initially made this process before I realized that all floats are stored using this standard regardless of computer hardware. The only differences that I know of are word order.

Option Explicit

Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _

(ByRef lpDest As Any, ByRef lpSrc As Any, ByVal ByteCount As Long)

‘—————————————————————————————

‘ Procedure : IEEE32toDouble

‘ DateTime : 12/12/2001 08:10

‘ Author : JackmanG

‘ Purpose :

‘ Convert Single Precision IEEE 754 Floating Point Integer Pairs (16 bit Integers) to

‘ their floating point representation. Note that the resulting value is actually

‘ a Single precision value inserted into a double precision variable.

‘ Originally this conversion was done using an algorithm derived from the internet

‘ but later I discovered that I could let the OS do the work since ALL floating

‘ point variables are internally represented in the IEEE format. Therefore, it

‘ is a simple task to simply copy the integer bits into a Single variable and the

‘ conversion is done automatically.

‘—————————————————————————————

‘ For reference, IEEE format is as follows:

‘ INPUT is two Integers representing a 32bit IEEE 754 value

‘ 32 bit Value Format: SEEEEEEEEMMMMMMMMMMMMMMMMMMMMMMM

‘ S = Sign bit (1)

‘ E = Exponent bits (8)

‘ M = Mantissa Bits (23)

Function IEEE32toDouble(ByVal x1 As Integer, ByVal x2 As Integer) As Double

Dim nInt(1) As Integer

Dim fTemp As Single

Dim sTemp As String

‘ Swap the 2 16bit words (Why? Because it works! It must be something to do with the PLC)

‘ Actually, when the PLC moves the 32 bit floats into 2 sequential 16 bit integers, it puts

‘ the values in bit order (W1 = High Word, W2 = Low Word). For some reason, the internal

‘ representation of a PC is the opposite. If you copy a long (32bit) integer into a two item

‘ integer (16 bit) array you see that the words are reversed (W1 = Low Word, W2 = High Word).

‘ Because we are using CopyMemory we need to match the input data to the PC’s internal storage

‘ format

On Error GoTo ErrHandler

nInt(0) = x2

nInt(1) = x1

CopyMemory fTemp, nInt(0), Len(fTemp) ‘ Copy back to single

‘ Validate the result to catch special IEEE cases or we’ll get errors

sTemp = CStr(fTemp) ‘ Get the string representation

If Not IsNumeric(sTemp) Then

‘ This should be "1.#INF" which is + infinity

IEEE32toDouble = 9999999999#

Else

‘ Valid Value

‘ it is intentional that we convert the string to double instead of the single value.

‘ I found that converting the single value to double is likely to result in values that

‘ are not properly represented.

‘ Example:

‘ CDbl(CStr(123.456)) = 123.456 <— As intended

‘ but

‘ CDbl(CSng(123.456)) = 123.456001281738 <— Yuck!

IEEE32toDouble = CDbl(sTemp)

End If

Exit Function

ErrHandler:

‘ This should be ‘overflow’ which is Not a Number

LogMessage "Error (" & Err.Description & ")Converting IEEE to Double for (N1,N2) = (" & x2 & "," & x1 & ")", SEVERITY_BROADCAST_ERROR, False

IEEE32toDouble = -9999999999#

‘Debug.Print "Error Converting IEEE to Double for (N1,N2) = (" & x2 & "," & x1 & ")"

End Function

‘—————————————————————————————

‘ Procedure : DoubleToIEEE32

‘ DateTime : 12/12/2001 09:37

‘ Author : JackmanG

‘ Purpose :

‘ Convert a Double Precision VB value to a Single Precision (32 bit) IEEE 754 encoded

‘ integer pair (16 bit integers).

‘ Note that the input value is a double and the output is single so precision can be

‘ lost. Also note that this routine is designed to be used with a PLC where the

‘ integer values are swapped vs. the PC’s internal storage.

‘—————————————————————————————

Public Sub DoubleToIEEE32(ByVal dValue As Double, ByRef nI1 As Integer, ByRef nI2 As Integer)

Dim fValue As Single

Dim nInt(1) As Integer

fValue = CSng(dValue)

CopyMemory nInt(0), fValue, Len(fValue) ‘ copy from Single to Int Array

‘ Internally, the Low Word is word 1 and High Word is word 2.

‘ Swap them to make it like the PLC guys do it.

nI1 = nInt(1)

nI2 = nInt(0)

End Sub

**This commented section here was my original attempt, which is clearly more difficult!**

‘Function IEEEtoDouble(ByVal x1 As Integer, ByVal x2 As Integer) As Double

‘’ IEEE format is as follows:

‘’ INPUT is two Integers representing a 32bit IEEE value

‘’ 32 bit Value Format: SEEEEEEEEMMMMMMMMMMMMMMMMMMMMMMM

‘’ S = Sign bit (1)

‘’ E = Exponent bits (8)

‘’ M = Mantissa Bits (23)

‘ Dim Sign As Integer

‘ Dim Exponent As Integer

‘ Dim i As Integer

‘

‘ Dim Mantissa As Double

‘ Dim MantissaE As Double

‘

‘ On Error GoTo ErrHandler

‘

‘ Exponent = x1 And 32640 ‘ Mask the exponent

‘ Exponent = Exponent / 128 ‘ Shift Right 7 places

‘ ‘Exponent = Exponent - 127 ‘ Bias

‘

‘ ‘ Get the sign value from the first bit of the first value

‘ Sign = x1 And 32768

‘ If Sign = 0 Then

‘ Sign = 1

‘ Else

‘ Sign = -1

‘ End If

‘

‘ ‘Mantissa = 1# ‘ the assumed 1 bit

‘ MantissaE = 0.5 ‘ 2^-1

‘ ‘get the first part of the mantissa from the last 7 bits of the first value

‘ For i = 6 To 0 Step -1

‘ If x1 And (2 ^ i) Then Mantissa = Mantissa + MantissaE

‘ MantissaE = MantissaE / 2#

‘ Next i

‘ ‘ get the next 16 bits of the mantissa from the second value

‘ For i = 15 To 0 Step -1

‘ If x2 And 2 ^ i Then Mantissa = Mantissa + MantissaE

‘ MantissaE = MantissaE / 2#

‘ Next i

‘

‘ If Exponent = 0 Then ‘test for Possible unnormalized result OR Zero

‘ ‘ if E=0 and M=0 then the result is 0 otherwise the result is called unnormalized and

‘ ‘ uses a different calculation

‘ If Mantissa = 0 Then IEEEtoDouble = 0 Else IEEEtoDouble = Mantissa * (2 ^ (-126)) * Sign

‘ ElseIf Exponent = 255 Then

‘ ‘ if E=0 then the result is Not a Number or infinity

‘ IEEEtoDouble = -9999

‘ Else

‘ ‘ otherwise, use the standard IEEE translation

‘ IEEEtoDouble = (Mantissa + 1#) * 2 ^ (Exponent - 127) * Sign

‘ End If

‘

‘ Exit Function

‘

‘ErrHandler:

‘ IEEEtoDouble = -9999

‘

‘End Function

Using the function:

Dim nValues() As Integer

‘ read the TWO integers and convert to double

If bSwapIEEEWords Then

‘ Swap Words

‘ Note that the swapping is actually done in the function

nValues = m_cPLCPDR.PDRValues(nPLCOffset, 2)

GetResultData = IEEE32toDouble(nValues(0), nValues(1))

Else

‘ Don’t swap words

‘ Required for PLC 5/250?!?

‘ Note that input values are swapped in the function

nValues = m_cPLCPDR.PDRValues(nPLCOffset, 2)

GetResultData = IEEE32toDouble(nValues(1), nValues(0))

End If

‘ There isn’t anything too complex going on here…