Processing hex from DS18B20 into binary then decimal

Recommended Posts

This is an example I wrote up as I worked through things. I have a microcontroller that sends data over Serial connection to the PC. The script (using CommMg.dll) from Martin, puts the hex in \$ComIn. For the example below, I've removed that to allow this example to run 'simulated' for anyone. Simply enter the hex into the \$ComIn variable. the hex data comes in from a DS18B20 temperature sensor. I needed to be able to convert this raw data into Celcius and/or Farenheit, including negative values. The 18B20 sends negatives as a twos compliment making the MSB a 1 for negative. In a positive number, the MSB is a 0. If anyone has any shortcuts I could have used, I'd be glad to hear them. Otherwise, the code below is fully functional.

#cs
\$ComIn can contain additional hex, only the first two bytes are parsed in this example.
No error checking is done to verify this is hex, but in my application it's in from Serial so will always be hex
Note the hex values are swapped on the sending (microcontroller) end to ease calculation.
In the example the actual hex is '07 D0', not 'D0 07'.
The 'real' data is in format LSB MSB, but we need this as MSB LSB to work with the script below
The hex is from a DS18B20 temperature sensor.
For testing here is a table of data and expected results
TempC    Binary          Hex     As entered in \$ComIn
+125      00000111 11010000  07 D0   D007
+85    00000101 01010000  05 50   5005
+25.0625  00000001 10010001  01 91   9101
+10.125   00000000 10100010  00 A2   A200
+0.5      00000000 00001000  00 08   0800
0        00000000 00000000  00 00   0000
-0.5      11111111 11111000  FF F8   F8FF
-10.125   11111111 01011110  FF 5E   5EFF
-25.0625  11111110 01101111  FE 6F   6FFE
-55    11111100 10010000  FC 90   90FC
#ce
#include <String.au3>  ; Needed only for _StringInsert to add a space to the binary output for visual clarity
\$ComIn = "0xD007aabbcc"  ; Simulated Hex in from Serial port connection.  aabbcc is to validate we only grabbed first 2 bytes and ignored the rest.
ConsoleWrite("Hex in: " & \$ComIn & @CRLF) ; Display raw \$ComIn value
\$TempC = BinaryMid(\$ComIn, 1, 2)  ; Store hex bytes for TempC
\$TempCache = \$TempC  ; For use in Binary generation below since BitShift is 'destructive'
\$Binary = ""
For \$loop = 1 To 16   ; Convert hex (two bytes) to Binary 1s and 0s
\$Binary = BitAND(\$TempCache, 1) & \$Binary
\$TempCache = BitShift(\$TempCache, 1)
Next
ConsoleWrite("\$Binary: " & _StringInsert(\$Binary," ",8) & @CRLF)  ; View resulting Binary value (_StringInsert to add a space between bytes)
\$TempCSign = StringLeft(\$Binary,1)  ; Get MSB (sign bit)
If \$TempCSign = 0 Then  ; Positive, so convert to Dec
\$TempC = BitOr(Binary(\$TempC),0)  ; Results in "proper" unmodified Decimal value [Unlike using 'Dec()']
;Same result but MUCH longer approach below
;\$TempC = (StringRight(\$Binary,1) * 1) + (StringMid(\$Binary,15,1) * 2) + (StringMid(\$Binary,14,1) * 4) + (StringMid(\$Binary,13,1) * 8) + (StringMid(\$Binary,12,1) * 16) + (StringMid(\$Binary,11,1) * 32) + (StringMid(\$Binary,10,1) * 64) + (StringMid(\$Binary,9,1) * 128) + (StringMid(\$Binary,8,1) * 256) + (StringMid(\$Binary,7,1) * 512) + (StringMid(\$Binary,6,1) * 1024)
ElseIf \$TempCSign = 1 Then  ; Negative so...
\$TempC = "-" & BitXOR(Binary(\$TempC), Binary("0xffff")) +1  ; get 2s compliment (with leading '-' sign)
EndIf
\$TempC = \$TempC * 0.0625  ; Multiply reading by resolution for actual value *C
\$TempF = (\$TempC * 1.8) + 32  ; Convert to *F
ConsoleWrite("TempC: " & \$TempC & "  TempF: " & \$TempF & @CRLF)  ; Display result for compare to table above
Edited by DrJeseuss
Share on other sites

I've got a couple general notes for you.

1) BinaryMid() returns a binary data type, so there's no reason to keep calling Binary(\$TempC).

2) \$TempC = BitOr(Binary(\$TempC), 0) is a bit silly. Number(\$TempC) is all you need.

3) BitXOR(Binary(\$TempC), Binary("0xffff")) is just confusing. The Bit* functions work with numbers, not binary types. AutoIt does a lot of internal conversion for you, but you're asking for trouble down the road. Better to use BitXOR(Number(\$TempC), 0xFFFF).

Share on other sites

I thought this is a half precision float so I decided to port a function from C. But it is not the same.

But here is is the function if someone needs it.

#Include <WinAPI.au3>

Local \$x = _Short2HalfPrecisionFloat(0x5640)
ConsoleWrite (\$x & @CR)

Local \$x = _Short2HalfPrecisionFloat(0x5A40)
ConsoleWrite (\$x & @CR)

Func _Short2HalfPrecisionFloat(\$half)
Local \$sign = BitAND(BitShift(\$half, 15), 0x0001)
Local \$exponent = BitAND(BitShift(\$half, 10), 0x001F)
Local \$fraction = BitAND(\$half, 0x03FF)

;ConsoleWrite(@CRLF & StringFormat("Sign: %i; Exponent: %i; Fraction: %i", \$sign, \$exponent, \$fraction) & @CRLF)

If \$exponent = 0 Then
If \$fraction = 0 Then
Return BitShift(\$sign, -31)
Else
While Not (BitAND(\$fraction, 0x0400))
\$fraction = BitShift(\$fraction, -1)
\$exponent -= 1
WEnd
\$exponent += 1
\$fraction = BitAND(\$fraction, BitNOT(0x0400))
EndIf
ElseIf \$exponent = 31 Then
If \$fraction = 0 Then
Return BitOR(BitShift(\$sign, -31), 0x7F800000)
Else
Return BitOR(BitShift(\$sign, -31), 0x7F800000, BitShift(\$fraction, -13))
EndIf
EndIf

\$exponent += 127 - 15
\$fraction = BitShift(\$fraction, -13)
Return StringFormat("%f", _WinAPI_IntToFloat(BitOR(BitShift(\$sign, -31), BitShift(\$exponent, -23), \$fraction)))
EndFunc

Programming today is a race between software engineers striving to
build bigger and better idiot-proof programs, and the Universe
trying to produce bigger and better idiots.
So far, the Universe is winning.

Share on other sites

I've got a couple general notes for you.

1) BinaryMid() returns a binary data type, so there's no reason to keep calling Binary(\$TempC).

2) \$TempC = BitOr(Binary(\$TempC), 0) is a bit silly. Number(\$TempC) is all you need.

3) BitXOR(Binary(\$TempC), Binary("0xffff")) is just confusing. The Bit* functions work with numbers, not binary types. AutoIt does a lot of internal conversion for you, but you're asking for trouble down the road. Better to use BitXOR(Number(\$TempC), 0xFFFF).

I've made a few changes to my script to simplify things as suggested by wraithdu. Thanks! This was my first go with this type of data and using the Bit* functions so I knew I had room to grow here. I also modified a bit so it's getting the SignBit from raw data. This will eliminate the 'need' to have the binary 1s and 0s, though I've left that bit in also for visual appeal.

#cs
\$ComIn can contain additional hex, only the first two bytes are parsed in this example.
No error checking is done to verify this is hex, but in my application it's in from Serial so will always be hex
Note the hex values are swapped on the sending (microcontroller) end to ease calculation.
In the example the actual hex is '07 D0', not 'D0 07'.
The 'real' data is in format LSB MSB, but we need this as MSB LSB to work with the script below
The hex is from a DS18B20 temperature sensor.
For testing here is a table of data and expected results
TempC    Binary          Hex     As entered in \$ComIn
+125      00000111 11010000  07 D0   D007
+85       00000101 01010000  05 50   5005
+25.0625  00000001 10010001  01 91   9101
+10.125   00000000 10100010  00 A2   A200
+0.5      00000000 00001000  00 08   0800
0         00000000 00000000  00 00   0000
-0.5      11111111 11111000  FF F8   F8FF
-10.125   11111111 01011110  FF 5E   5EFF
-25.0625  11111110 01101111  FE 6F   6FFE
-55       11111100 10010000  FC 90   90FC
#ce
#include <String.au3>  ; Needed only for _StringInsert to add a space to the binary output for visual clarity
\$ComIn = "0x90FCaabbcc"  ; Simulated Hex in from Serial port connection.  aabbcc is to validate we only grabbed first 2 bytes and ignored the rest.
ConsoleWrite("Hex in: " & \$ComIn & @CRLF) ; Display raw \$ComIn value
\$TempC = BinaryMid(\$ComIn, 1, 2)  ; Store hex bytes for TempC
\$SignBit = BitShift(\$TempC, 15)
#region ; This section produces the binary 1s and 0s for visualization
\$TempCache = \$TempC  ; 'Disposable' variable for use in Binary generation below
\$Binary = ""
For \$loop = 1 To 16   ; Convert hex (two bytes) to Binary 1s and 0s
\$Binary = BitAND(\$TempCache, 1) & \$Binary
\$TempCache = BitShift(\$TempCache, 1)
Next
ConsoleWrite("\$Binary: " & _StringInsert(\$Binary," ",8) & @CRLF)  ; View resulting Binary value (_StringInsert to add a space between bytes)
#endregion
If \$SignBit = 0 Then  ; Positive, so convert to Dec
\$TempC = Number(\$TempC)  ; Results in "proper" unmodified Decimal value [Unlike using 'Dec()']
;Same result but MUCH longer approach below
;\$TempC = (StringRight(\$Binary,1) * 1) + (StringMid(\$Binary,15,1) * 2) + (StringMid(\$Binary,14,1) * 4) + (StringMid(\$Binary,13,1) * 8) + (StringMid(\$Binary,12,1) * 16) + (StringMid(\$Binary,11,1) * 32) + (StringMid(\$Binary,10,1) * 64) + (StringMid(\$Binary,9,1) * 128) + (StringMid(\$Binary,8,1) * 256) + (StringMid(\$Binary,7,1) * 512) + (StringMid(\$Binary,6,1) * 1024)
ElseIf \$SignBit = 1 Then  ; Negative so...
\$TempC = "-" & BitXOR(Number(\$TempC), 0xffff) +1  ; get 2s compliment (with leading '-' sign)
EndIf
\$TempC = \$TempC * 0.0625  ; Multiply reading by resolution for actual value *C
\$TempF = (\$TempC * 1.8) + 32  ; Convert to *F
ConsoleWrite("TempC: " & \$TempC & "  TempF: " & \$TempF & @CRLF)  ; Display result for compare to table above
Edited by DrJeseuss
Share on other sites

I should have thought of this before, but you could also just test the sign bit like this:

If (BitAND(Number(\$TempC), 0x8000) = 0x8000) Then
Edited by wraithdu
Share on other sites

I should have thought of this before, but you could also just test the sign bit like this:

If (BitAND(Number(\$TempC), 0x8000) = 0x8000) Then

In my edited code, I'm using a bit different approach. Is there anything I'm missing or will mine do basically the same result? I see your method becomes part of the expression while mine currently uses a "flag" variable (\$SignBit). I suppose I could eliminate the \$SignBit by doing the following. I've tried it and it seems to work... and a bit less code than you suggested.

If BitShift(\$TempC, 15) = 0 Then  ; Positive, so convert to Dec
\$TempC = Number(\$TempC)  ; Results in "proper" unmodified Decimal value [Unlike using 'Dec()']
ElseIf BitShift(\$TempC, 15) = 1 Then  ; Negative so...
\$TempC = "-" & BitXOR(Number(\$TempC), 0xffff) +1  ; get 2s compliment (with leading '-' sign)
EndIf

I'm only just beginning to grasp the power of these Bit* functions and binary math. I may have questions for you down the road. Thanks again for the extra set of eyes.

Share on other sites

The more I think about this the more I wonder if I've reinvented the wheel. Maybe I've missed a simple function that will do the same. Is there a simper way to take my hex in it's 'natural' form of LSB MSB (0x07D0) to get 125, or (0xFE6F) to get -25.0625, etc (per the table in my example)?

Share on other sites

I think you've simplified it as much as possible.

I personally wouldn't use the BitShift function to test for the sign bit like that, just because you're dealing with a 16 bit value. The Bit* functions work with 32-bit numbers, and there isn't any documentation as to how those upper 16 bits are padded when feeding a 16-bit number to BitShift. I would assume 0's, but I can't say that is true in all cases. There's also the issue of propagation of the sign bit with BitShift and 32-bit numbers. For example

\$n = 0x80000000
ConsoleWrite(Hex(BitShift(\$n, 16)) & @CRLF)
; output = FFFF8000

Anyway, that's just me being weird.

Edited by wraithdu
Share on other sites

I think you've simplified it as much as possible.

I personally wouldn't use the BitShift function to test for the sign bit like that, just because you're dealing with a 16 bit value. The Bit* functions work with 32-bit numbers, and there isn't any documentation as to how those upper 16 bits are padded when feeding a 16-bit number to BitShift. I would assume 0's, but I can't say that is true in all cases.

From my testing and trying to break this I've seen that the padding is in fact 0's, though it may not always be true. I've yet to see an exception though so far.

There's also the issue of propagation of the sign bit with BitShift and 32-bit numbers. For example

\$n = 0x80000000
ConsoleWrite(Hex(BitShift(\$n, 16)) & @CRLF)
; output = FFFF8000

Anyway, that's just me being weird.

Can you explain what 'propogation of the sign bit' means? I'm not entirely clear on hat's happening in your example. It does appear that I need to ensure only proper byte counts are used for input to this script.
Share on other sites

As with your data, the MSB is the sign bit for signed 32-bit integers, ie

0x80000000 =

10000000 00000000 00000000 00000000

^

If you BitShift this 16 bits to the right as I did above, AutoIt returns

0xFFFF8000 =

11111111 11111111 10000000 00000000

The sign bit is propagated down the line.

Share on other sites

As with your data, the MSB is the sign bit for signed 32-bit integers, ie

0x80000000 =

10000000 00000000 00000000 00000000

^

If you BitShift this 16 bits to the right as I did above, AutoIt returns

0xFFFF8000 =

11111111 11111111 10000000 00000000

The sign bit is propagated down the line.

I think I understand now... despite using a 16-bit hex, autoit (and BitShift) are still treating this as a 32-bit, padding as needed to make the shift. As such, I may have issues if I BitShift PAST my 16 bits... is this correct. So, in short, if I'm careful to stay in bounds of my 16 bits (in this example) then I should not enter this padded region and therefore my data would remain accurate, right?

Share on other sites

The 32 bit integer will always be interpreted as having the MSB as the first bit. If you wish to redefine this, then I suggest you set the MSB to zero and only work within the confines of the other 31 bits.

Create an account

Register a new account