0

I am using a for loop to copy an array from a UART RX buffer to memory.

This looks like as follows:

UART2_readTimeout(uartR2, rxBuf3, 54, NULL, 500);
GPIO_toggle(CONFIG_GPIO_LED_3);

if ((rxBuf3[4] == 0x8C) && (rxBuf3[10] != 0x8C)) {
    int i;
    for (i = 0; i < 47; i++) {
        sioR2[i]=rxBuf3[i];
    }

I want to then use a struct such as the following to make it possible to use dot notation when working with and organizing the data:

typedef struct
{
  uint16_t voltage;
  uint16_t current;
  uint16_t outTemp;
  uint16_t inTemp;
  uint16_t status;
  uint32_t FaultA;
  uint32_t FaultB;
  uint32_t FaultC;
  uint32_t FaultD;
  uint8_t softwareMode;
  uint8_t logicLoad;
  uint8_t outputBits;
  uint16_t powerOut;
  uint32_t runHours;
  uint16_t unitAddresses[6];
} unitValues;

Assuming the total length of these are the same, is it possible to perform a memcpy on the entire array to a single instance of the struct?

Array  : 001110101....110001
         |||||||||||||||||||   <- memcpy
         vvvvvvvvvvvvvvvvvvv
Struct : 001110101....110001
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
Adam
  • 11
  • 2
  • Possibly. Can you tell us what structure packing options you have set for your compiler/platform? The odd number of `uint8_t` in the middle of the structure looks a bit problematical, though. – Adrian Mole Jan 27 '21 at 19:32
  • This is just an example I have not aligned the two I gave there. My rxBuf3 is in uint8_ts so I can do any number in theory? I am using Code Composer Studio and am not sure about the structure packing options. – Adam Jan 27 '21 at 19:41
  • The odd numbers of consecutive `uint16_t` members in several places could also present an issue. – John Bollinger Jan 27 '21 at 19:42
  • Also, you appear to be copying 54 bytes of data into your array, but the sum of the sizes of your struct members is only 47. – John Bollinger Jan 27 '21 at 19:43
  • Do you need both the array and the struct? If it were me, I would be looking to read the data directly into the struct, on a member-by-member basis. – John Bollinger Jan 27 '21 at 19:46
  • Updated there John to reflect 47. – Adam Jan 27 '21 at 19:46
  • I dont need the array at all but because of the drivers I am using, it expects a uint8_t array for the buffer. I am not going to rewrite the driver for my application but I wish this were the case. :) – Adam Jan 27 '21 at 19:47

3 Answers3

1

Provided that your C implementation offers a way to ensure that the layout of your structure is the same as the layout that the driver in question uses for writing the buffer, a pretty good way to go about this would be to have the driver write directly into the structure. I'm inferring the signature of the driver function here, but that would probably be something like:

UART2_readTimeout(uartR2, (uint8_t *) &values, 54, NULL, 500);

Assuming that uint8_t is an alias for unsigned char or maybe char, it is valid to write into the representation of the structure via a pointer of type uint8_t *. Thus, this avoids you having to make a copy.

The trick, however, is the structure layout. Supposing that you expect the data to be laid out as the structure members given, in the order given, with no gaps, such a structure layout would prevent structure instances being positioned in memory so that all members are aligned on addresses that are multiples of their sizes. Depending on the alignment rules of your hardware, this might be perfectly fine, but probably either it would slow accesses to some of the members, or it would make attempts to access some of the members crash the program.

if you still want to proceed then you will need to check your compiler's documentation for information about how to get the wanted layout of your structure. You might look for references to structure "packing", structure layout, or structure member alignment. There is no standard way to do this -- if your C implementation supports it at all then that constitutes an extension, with implementation-specific details.

All the same issues and caveats would apply to using memcpy to copy the buffer contents onto an instance of your structure type, so if you don't multiple copies of the data and you can arrange to make bulk copy onto the structure work, then you're better off writing directly onto the structure than writing into a separate buffer and then copying.


On the other hand, the safe and standard alternative would be to allow your implementation to lay out the structure however it thinks is best, And to copy the data out of your buffer into the structure in member-by-member fashion, with per-member memcpy()s. Yes, the code will be a bit tedious, but it will not be sensitive to alignment-related issues, nor even to reordering structure members or adding new ones.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
0

You have to change the packing align to 1 byte for the structure.

#pragma pack(1) /* change */
typedef struct {
...
}
#pragma pack() /* restore */
P L
  • 114
  • 5
0

In theory, you can use memcpy() to set the member fields of a struct from the elements of a byte array. However, you will need to be very careful to prevent your compiler from adding 'empty' fields to your struct (see: Structure padding and packing) unless those empty fields are taken into account when loading the data into the source array. (The elements of the source array will be packed into contiguous memory.)

Different compilers use different command-line and/or #pragma options to control structure packing but, for the MSVC compiler, you can use the #pragma pack(n) directives in your source code or the /Zp command-line switch.

Using the MSVC compiler, the structure you have provided will have a total size of 47 bytes only if you have single-byte packing; for default packing, the size will be 52 bytes.

The following code block shows where these 'extra' bytes will be inserted for different packing sizes.

#pragma pack(push, 1) // This saves the current packing level then sets it to "n" (1, here)
typedef struct {
    uint16_t voltage;
    uint16_t current;
    uint16_t outTemp;
    uint16_t inTemp;
    uint16_t status;
    // 4+ byte packing will insert two bytes here
    uint32_t FaultA;
    uint32_t FaultB;
    uint32_t FaultC;
    uint32_t FaultD;
    uint8_t softwareMode;
    uint8_t logicLoad;
    uint8_t outputBits;
    // 2+ byte packing will insert one byte here
    uint16_t powerOut;
    // 4+ byte packing will insert two bytes here
    uint32_t runHours;
    uint16_t unitAddresses[6];
} unitValues;
#pragma pack(pop) // This restores the previous packing level

So, the sizeof(unitValues) will be:

  • 47 bytes when using #pragma pack(1)
  • 48 bytes when using #pragma pack(2)
  • 52 bytes when using #pragma pack(4) (or any higher/default value)
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • 1
    Yet if they can arrange it so that `memcpy()` does the right thing, then it would be to their advantage to have the driver write directly to the structure, instead of to an intermediate buffer that serves only as a staging area. – John Bollinger Jan 27 '21 at 20:19
  • @JohnBollinger You'll typically want multiple buffers for UART RX regardless. One that the UART is writing to (using interrupts or DMA) and another with complete data that the application is using. Then you swap between these buffers by changing a pointer, not by calling slow `memcpy`. – Lundin Jan 28 '21 at 09:14
  • Fair enough, @Lundin. My comment is about the data type of the buffer and avoiding `memcpy()`, so it is consistent with the behavior you describe. – John Bollinger Jan 28 '21 at 13:19