2

I'm currently in the process of re-writing a program to process data received over a serial connection using the RDM protocol, each packet is received by a UART and has a specific structure but may vary in length, a packet structure example is below, assuming the number of bytes in the packet to be n (this may change depending on the contents of the packet)

What I want to do is define a struct in my C code that has the various parameters defined, but to be able to read and write bytes to/from the struct from the UART as though the struct is just an array of uint8_t. My issue with this is that I have read that structs may not always be stored in continuous sections of memory, so taking &RDMPacket1 and increment through the struct may end up with the data not being in the right place.

My other problem is that if I have an array to store a packet data of the maximum possible length (220 bytes) inside the struct, then the checksum at the end of the packet would be written into the wrong place. What methods could be used to receive the data and place it into the struct?

Example packet definition (shortened from standard)

Byte     | Description
0        | START Code - Constant, can be ignored
1        | Sub-Start Code - Contains command for device to process
2        | Message Length - Points to byte number of Checksum High (up to 255)
3-8      | Destination UID - Unique ID of packet Destination
9-14     | Source UID - Unique ID of packet Source
15       | Transaction Number - ID of transaction between controller and responder
16-(n-2) | Data (up to 220 bytes long)
n-1      | Checksum High
n        | Checksum Low

This is an example of a struct to hold the a packet of maximum possible length:

struct RDMPacket
{
    uint8_t     subStartCode;
    uint8_t     messageLength;
    uint32_t    destinationUID;
    uint32_t    sourceUID;
    uint8_t     transactionNumber;
    uint8_t     portID;
    uint8_t     messageCount;
    uint8_t     subDevice;
    uint8_t     commandClass
    uint8_t     parameterID;
    uint8_t     parameterDataLength;
    uint8_t     parameterData[220];
    uint16_t    checksum
} RDMPacket1;
JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
RobbG
  • 371
  • 5
  • 14
  • 2
    I don't think uint32_t has 6 bytes... and honestly, maybe it'll be a better idea not to use struct for this, you'll have to deal with things like padding, alignment etc.. not worth the trouble imo – MightyPork Jan 21 '15 at 15:01
  • The checksum appears at a variable position; that at least requires special attention. There's likely to be padding in the structure after `messageLength` and before `destinationUID`, too. That means the structure cannot accurately represent the byte stream on the wire. You're better off not trying to serialize/deserialize the structure directly (it won't work accurately). Use code to pack/unpack the relevant data into/out of an array of bytes. – Jonathan Leffler Jan 21 '15 at 16:12
  • @JonathanLeffler That is true, I think I'll take this approach to it. Shouldn't be too hard to have it packed into the struct as it arrives. Thanks! – RobbG Jan 22 '15 at 10:37

1 Answers1

4

The problem that you are describing can arise when you are dealing with a non-byte aligned memory structure. In this case each struct field will have the specific alignment. I.e., if the alignment is 4 bytes, each field will start on an address that is divisible by 4. To avoid this, you can use GCC's attribute packed for the structure, that instructs the compiler to pack the structure to a minimal memory. In other compilers there is #pragma pack or some other corresponding compiler directives for this purpose. To make sure that your struct is packed, you can check it's size with sizeof and compare it to the expected size.

Eugene Sh.
  • 17,802
  • 8
  • 40
  • 61
  • Agreed. The answer might be to pre-pad his structure with 2 bytes to tip the structure into alignment. – Persixty Jan 21 '15 at 15:21
  • In the sample structure, the only place where padding is likely to be needed is after `messageLength`. If `uint32_t` members must be aligned on a 4-byte boundary (a common requirement), then there will be 2 bytes of padding between `messageLength` and `destinationUID`. Each member of the structure has to be placed such that it will be aligned correctly for its type, and the structure as a whole has to be allocated such that its member with the most stringent alignment requirement is always properly aligned. But there'll be no padding between consecutive `uint8_t` members, for example. – Jonathan Leffler Jan 21 '15 at 15:45