Search code examples
perlcpu-wordmessagepack

Perl pack()ing a message?


Background

I have a device connected to my machine on a serial port that is message oriented, where each incoming command message is exactly eight 16-bit words. The most significant byte of each word is received first and the least significant byte received second. Each header word is a command identifier (ranging from 0 - 255). The format is defined like so:

XXXX    Header Word (Placeholder for 16-bit Hexadecimal value)
XXXX    Data Word
XXXX    Data Word
XXXX    Data Word
XXXX    Data Word
XXXX    Data Word
XXXX    Data Word
XXXX    Checksum Word (Result of Exclusive OR on header word and all six data words)

The format for the command I need to send, header 0x5D, is defined like so:

Word 1: Header = 005Dh
Word 2: Mode (values can be 2, 3, 6, 8, 9)
Words 3-7: Not significant
Word 8: 16 bit checksum 

Questions (all related)

I have this example from other source code that apparently sends this message correctly, but I'm not sure what is going on, even after reading perldoc on pack.

pack("v"x8, 0x00, 0x5D, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x5D);
  1. "v" means little endian, but I cannot find any reference on the x8 following it.
  2. And according to the format of the message, shouldn't the 0x5D value be right after the "v"x8, not after the 0x00?
  3. The message is supposed to have eight 16-bit words, so why are there 16 and not 8? Some are 0x00, and not 0x0? Does that make a difference?

Solution

  • pack "v"x8 (which results in pack "vvvvvvvv", which can be written as pack "v8" for short) packs eight 16-bit numbers, yet you passed sixteen.

    If you have sixteen bytes already, you want

    pack 'C16', 0x00, 0x5D, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x5D
    

    But I would expect you to have eight 16-bit words instead, so you'd use

    pack 'n8', 0x005D, 0x0009, 0x0000, 0x0000, 0x0000, 0x0000, 0x0009, 0x005D
    

    Note the use of n instead of v to get the desired byte order.

    pack 'v', 0x1234      => "\x34\x12"   Most significant byte last
    pack 'n', 0x1234      => "\x12\x34"   Most significant byte first
    

    Proof that both methods are equivalent:

    my $method1 =  pack 'C16',
        0x00, 0x5D, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x5D;
    
    my $method2 = pack 'n8',
        0x005D, 0x0009, 0x0000, 0x0000, 0x0000, 0x0000, 0x0009, 0x005D;
    
    print($method1 eq $method2 ? "yes" : "no", "\n");  # yes