Question

I have a set of 32-bit registers for a peripheral in an embedded application(ARM bare-metal), with the following byte addresses.

CTL 0x0;
STAT 0x4
TXR 0x8 <-- Discontinuous address
RXR 0x20
DAT1 0x30 <-- Discontinuous address
DAT2 0x40 <-- Discontinuous address
and so on

I want to group all these registers into a C struct (Its a packed struct)

struct my_peri {
     uint32_t CTL;
     uint32_t STAT;
     uint32_t TXR;
     uint32_t RXR;
     uint32_t DAT1;
     uint32_t DAT2;
};

struct my_peri* pPeri0 = (uint32_t*) (BASE_ADDRESS_OF_MY_PERI_0);

Now if i access

pPeri->RXR;  // This will point to (BASE_ADDRESS + 0x10)
             // But the actual address i want to refer is (BASE_ADDRESS + 0x20)

In order to get the right the address right i manually added some elements between

struct my_peri {
     uint32_t CTL;
     uint32_t STAT;
     uint32_t TXR;
     uint32_t RESERVED[4]; // 0x10 to 0x1c
     uint32_t RXR;
     uint32_t RESERVED_1[3]; // 0x24-0x2c
     uint32_t DAT1;
     uint32_t RESERVED_2[3]; // 0x34-0x3c
     uint32_t DAT2;
};

But any access to RESERVED, RESERVED_1 and RESERVED_2 will give error as per the peripheral specs.

Is there a way to add address spacing between the struct elements?
Without adding RESERVED elements

If not, is there a way to group these registers into a single data structure?.
With each register pointing to the right address.

I'm using ARM-GCC toolchain.

Was it helpful?

Solution

Yes, you can create unnamed fields within a struct using "bit fields":

struct my_peri {
     uint32_t CTL;
     uint32_t STAT;
     uint32_t TXR;
     uint32_t : 32;
     uint32_t RXR;
     uint32_t : 32;
     uint32_t : 32;
     uint32_t : 32;
     uint32_t DAT1;
     uint32_t : 32;
     uint32_t : 32;
     uint32_t : 32;
     uint32_t DAT2;
};

Unfortunately there's no array syntax; you can collapse pairs of :32 to :64 if you want by changing the type to uint64_t.

An alternative, if your fields are all the same type as in this example, would be to treat the whole thing as an array of integers and index into it using an enum { CTL = 0, STAT = 1, TXR = 2, RXR = 4, ... }.

OTHER TIPS

To answer both your questions, You will inevitably end up with some kind of fake padding element as C has no provision to force a particular layout for a struct.

However perhaps a nicer way of achieving this given you ultimately have a sparse layout would be to use a struct full of of pointers. This no longer restricts you as to layout in any way and in most cases if you define the pointers as const gcc will optimize out the dereference completely and should even optimize out the whole array. I admit this is not exactly what you asked for but I think it is still a good way of solving the problem. You are also no longer relying on a C extension and hoping the compiler respects your packing and layout requirements.

#include <stdio.h>
#include <stddef.h>
#include <inttypes.h>

struct my_peri {
     uint32_t CTL;
     uint32_t STAT;
     uint32_t TXR;
     uint32_t RXR __attribute__((aligned (32)));
     uint32_t DAT1 __attribute__((aligned (16)));
     uint32_t DAT2 __attribute__((aligned (16)));
};

int main(void)
{
     printf("RXR  @%x\n", (unsigned) offsetof(struct my_peri, RXR));
     printf("DAT1 @%x\n", (unsigned) offsetof(struct my_peri, DAT1));
     printf("DAT2 @%x\n", (unsigned) offsetof(struct my_peri, DAT2));
     return 0;
}

Output:

RXR  @20
DAT1 @30
DAT2 @40

Note that aligned values have to be powers of two. But that works out here.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top