pack/unpack help please

D

Dave Saville

I am using perl on a Rasberry pi to read an i2c device using the hipi
library.

@temp = $dev->bus_read( MPU6050_TEMP_OUT_H, 2 );

Returns two bytes as an array of two values. High byte, low byte.

For example I may get:

$temp[0] = 244; # 0xFD
$temp[1] = 224; # 0xE0

Which is -2848. I (eventually) came up with this

$temp = unpack 's', pack 'CC', reverse @temp;

Is there a better method? Also why does

$temp = unpack 'n', pack 'CC', @temp;

Not give the same value?

TIA
 
R

Rainer Weikusat

Dave Saville said:
I am using perl on a Rasberry pi to read an i2c device using the hipi
library.

@temp = $dev->bus_read( MPU6050_TEMP_OUT_H, 2 );

Returns two bytes as an array of two values. High byte, low byte.

For example I may get:

$temp[0] = 244; # 0xFD

This is 0xf4, not 0xfd.
$temp[1] = 224; # 0xE0

Which is -2848. I (eventually) came up with this

$temp = unpack 's', pack 'CC', reverse @temp;

Is there a better method?

What's you definition of 'better' here? You can generally build the
number by 'putting the bytes in the right place', ie

$temp[0] << 8 | $temp[1]

If you want it to be interpreted as signed, two ways to achieve that
would be

($temp[0] << 8 | $temp[1]) - ($temp[0] & 0x80 && 0x1000)

and

use integer;
($temp[0] << 8 | $temp[1]) ^ ($temp[0] & 0x80 && HIBITS)

with HIBITS defined as

use constant HIBITS => -1 & ~0xffff;

both are faster (for me) than your approach, OTOH, you'll very likely
freak out people to whom it never occured that computers actually work
with binary numbers and that the bit operators are useful.

sample code:
-------------
use Benchmark;

use constant HIBITS => -1 & ~0xffff;

@t = (0xf4, 0xe0);

timethese(-3,
{
pack => sub {
return unpack('s', pack('CC', reverse(@t)));
},

calc => sub {
use integer;
return ($t[0] << 8 | $t[1]) ^ ($t[0] & 0x80 && HIBITS);
}});
 
R

Rainer Weikusat

Rainer Weikusat said:
Dave Saville said:
I am using perl on a Rasberry pi to read an i2c device using the hipi
library.

@temp = $dev->bus_read( MPU6050_TEMP_OUT_H, 2 );

Returns two bytes as an array of two values. High byte, low byte.
[...]

use integer;
($temp[0] << 8 | $temp[1]) ^ ($temp[0] & 0x80 && HIBITS)

with HIBITS defined as

use constant HIBITS => -1 & ~0xffff;

The xor is somewhat out-of-place here because there are no overlapping
1-bits in both numbers. | can be used as well.
 
D

Dave Saville

Dave Saville said:
I am using perl on a Rasberry pi to read an i2c device using the hipi
library.

@temp = $dev->bus_read( MPU6050_TEMP_OUT_H, 2 );

Returns two bytes as an array of two values. High byte, low byte.

For example I may get:

$temp[0] = 244; # 0xFD

This is 0xf4, not 0xfd.

Yup - typo and/or brain to keyboard. :)
$temp[1] = 224; # 0xE0

Which is -2848. I (eventually) came up with this

$temp = unpack 's', pack 'CC', reverse @temp;

Is there a better method?

What's you definition of 'better' here? You can generally build the
number by 'putting the bytes in the right place', ie

$temp[0] << 8 | $temp[1]

That's what I thought at first, but the sign stumped me.
If you want it to be interpreted as signed, two ways to achieve that
would be

($temp[0] << 8 | $temp[1]) - ($temp[0] & 0x80 && 0x1000)

and

use integer;
($temp[0] << 8 | $temp[1]) ^ ($temp[0] & 0x80 && HIBITS)

with HIBITS defined as

use constant HIBITS => -1 & ~0xffff;

Hmm I see how the second one works but the first doesn't :

use strict;
use warnings;

my @temp;
$temp[0] = 244; # 0xF4
$temp[1] = 224; # 0xE0
printf "%X %X\n", @temp;
printf "%X\n", $temp[0] & 0x80;
printf "%X\n", $temp[0] & 0x80 && 0x1000;
printf "%X\n", ($temp[0] << 8 | $temp[1]) - ($temp[0] & 0x80 &&
0x1000);
printf "%d\n", ($temp[0] << 8 | $temp[1]) - ($temp[0] & 0x80 &&
0x1000);
use integer;
use constant HIBITS => -1 & ~0xffff;
printf "%X\n", HIBITS;
printf "%X\n", $temp[0] & 0x80;
printf "%X\n", $temp[0] & 0x80 && HIBITS;
printf "%X\n", ($temp[0] << 8 | $temp[1]) ^ ($temp[0] & 0x80 &&
HIBITS);
printf "%d\n", ($temp[0] << 8 | $temp[1]) ^ ($temp[0] & 0x80 &&
HIBITS);

[T:\tmp]try.pl
F4 E0
80
1000
E4E0
58592
FFFF0000
80
FFFF0000
FFFFF4E0
-2848
 
R

Rainer Weikusat

Dave Saville said:
Dave Saville said:
I am using perl on a Rasberry pi to read an i2c device using the hipi
library.

@temp = $dev->bus_read( MPU6050_TEMP_OUT_H, 2 );

Returns two bytes as an array of two values. High byte, low byte.

For example I may get:

$temp[0] = 244; # 0xFD
[...]
If you want it to be interpreted as signed, two ways to achieve that
would be

($temp[0] << 8 | $temp[1]) - ($temp[0] & 0x80 && 0x1000)
[...]

Hmm I see how the second one works but the first doesn't :

My bad. The final number in the second term should be 0x10000 (65536),
not 0x1000 (4096).

perl -e 'print((0xf4 << 8 | 0xe0) - (0xf4 & 0x80 && 0x10000))'

Both rely on negative integers being represented as two's complement. In
this case, a negative n-bit integer is encoded as 'distance' from 2**n,
ie 0xffff aka 65535 is -1.
 
D

Dave Saville

My bad. The final number in the second term should be 0x10000 (65536),
not 0x1000 (4096).

What's a factor of 16 between friends? :)
perl -e 'print((0xf4 << 8 | 0xe0) - (0xf4 & 0x80 && 0x10000))'

Both rely on negative integers being represented as two's complement. In
this case, a negative n-bit integer is encoded as 'distance' from 2**n,
ie 0xffff aka 65535 is -1.

I am happy playing with bits - I just could not see what you were
trying to do there. Whereas your second method was obvious what it
did.

Just for fun I added another I dreamed up to your benchmark

kalk => sub {
my $x = $t[0] << 8 | $t[1];
return $t[0] & 0x80 ? ~(~$x & 0xffff) : $x;

Which sits between the two.

Thanks.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,902
Latest member
Elena68X5

Latest Threads

Top