How can I access variables in a packed structure without jitter?

I am using a packaged framework for communication using direct DMA access and here is my test code:

// structure for communication buf 1
typedef __packed struct _test1
{
    uint8_t a;
    uint32_t b;
    uint16_t c;
    uint16_t d;
    uint32_t e;
} test1;

// structure for communication buf 2
.
.
.
// structure for communication buf 3
.
.
.

// structure for communication buf set
typedef __packed struct _test2
{
    uint8_t dump[3];
    test1 t;
    // may have many other packed structure for communication buf
} test2;

#pragma anon_unions

typedef struct _test3
{
    union
    {
        uint32_t buf[4];
        __packed struct
        {
            __packed uint8_t dump[3];
            test1 t;
        };
    };
} test3;

test1 t1;
test2 t2;
test3 t3;

      

the size of these structures

sizeof(t1) = 13
sizeof(t2) = 16
sizeof(t3) = 16

      

if I want to access variable b, in order not to affect performance, read / write memory contents are required with aligned access, with the calculated offset manually

t3.buf[1]

      

but I cannot read / write variables in a structure without using uneven accesses

t2.t.b
t3.t.b

      

so I defined structs as following code, packed only variable a

typedef struct _test4
{
    __packed uint8_t a;
    uint32_t b;
    uint16_t c;
    uint16_t d;
    uint32_t e;
} test4;

typedef struct _test5
{
    __packed uint8_t dump[3];
    test4 t;
} test5;

test4 t4;
test5 t5;

      

although the access of all elements in the structure is aligned, but the addition is inserted either

sizeof(t4) = 16
sizeof(t5) = 20

      

so how can I define packed structs and access one variable in it without using non-uniform access (other than a)?

Many thanks for the help

+3


source to share


2 answers


Your question introduces two problems under the general title:

  • Communication between components and / or devices; it may or may not have the same basic representation of structs and integers, hence you are using a non-portable attribute __packed

    .
  • Access performance biased by alignment and / or data size; on the one hand, the compiler aligns the data to fall in line with the bus, but on the other hand, the data may take up too much space in your cache.

One is the real problem you want to solve, X, and the other is Y in your XY problem . Please avoid XY problems in the future.

Have you considered how to ensure that uint16_t

and uint32_t

will be large or medium, depending on your requirements? You must indicate that if you care about portability. I care about portability, so my answer will be focused. You can also see how optimal efficiency will be achieved. However, to do this transfer:

  • You must serialize your data using serialization functions to convert each member of your structure into a sequence of bytes using division and modulation (or left shift and binary and) operations.
  • Likewise, you must deserialize your data by multiplying and adding inverse operations (or right shift and binary or).


As an example, here's some code showing both the small end and the big endian for serialization and deserialization test1

:

typedef /*__packed*/ struct test1
{
    uint32_t b;
    uint32_t e;
    uint16_t c;
    uint16_t d;
    uint8_t a;
} test1;

void serialise_test1(test1 *destination, void *source) {
    uint8_t *s = source;
    destination->a = s[0];
    destination->b = s[1] * 0x01000000UL
                   + s[2] * 0x00010000UL
                   + s[3] * 0x00000100UL
                   + s[4];                 /* big endian */
    destination->c = s[5] * 0x0100U
                   + s[6];                 /* big endian */
    destination->d = s[7]
                   + s[8] * 0x0100U;       /* little endian */
    destination->e = s[9]
                   + s[10] * 0x00000100UL
                   + s[11] * 0x00010000UL
                   + s[12] * 0x01000000UL; /* little endian */
}

void deserialise_test1(void *destination, test1 *source) {
    uint8_t temp[] = { source->a
                     , source->b >> 24, source->b >> 16
                                      , source->b >> 8, source->b
                     , source->c >> 8, source->c
                     , source->d, source->d >> 8
                                , source->d >> 16, source->b >> 24 };
    memcpy(destination, temp, sizeof temp);
}

      


You may notice that I have removed the attribute __packed

and rearranged the elements so that the larger members precede (i.e. before) the smaller ones; this will probably reduce the indentation significantly. Functions allow you to convert between an array uint8_t

(which you send / receive from explorer, DMA or whatever) and a structure test1

, so this code is much more portable. You get the guarantees that this code provides regarding your protocol structure, where, as before, at the whim of the implementation, and two devices using two different implementations might disagree with the internal representation of integers, for example.

+4


source


You can hard-code all indices like

    typedef __packed struct _test1
    {
        uint8_t a;
        uint32_t b;
        uint16_t c;
        uint16_t d;
        uint32_t e;
    } test1;

    enum
    {
        a = 0,
        b = 1,
        c = 5,
        d = 7,
        e = 9,
    };

    test1 t1 = {1,2,3,4};//not sure if init lists work for packed values
    printf("%u", *(uint32_t*)((uint8_t*)&t1 + b));

      



Or you can use offsetof like

printf("%u", *(uint32_t*)((uint8_t*)&t1 + offsetof(test1, b)));

      

0


source







All Articles