0

I am writing my own header file for STM32F4. I want to access registers like this: GPIOB_device.GPIOB_MODER.B.MODER0 for bits and GPIOB_device.GPIOB_MODER.R for whole register.

struct GPIOB_tag {
    union {
        uint32_t R:32;              // 0x40020400 (32 bit)
        struct { 
            uint32_t MODER15:2;
            uint32_t MODER14:2; 
            uint32_t MODER13:2; 
            uint32_t MODER12:2;
            uint32_t MODER11:2;
            uint32_t MODER10:2;
            uint32_t MODER9:2;
            uint32_t MODER8:2;
            uint32_t MODER7:2;
            uint32_t MODER6:2;
            uint32_t MODER5:2;
            uint32_t MODER4:2;
            uint32_t MODER3:2;
            uint32_t MODER2:2;
            uint32_t MODER1:2;
            uint32_t MODER0:2;
        } B;
    } GPIOB_MODER;
};

#define GPIOB_device (*(volatile struct GPIOB_tag *)0x40020400)

STM32F4 manual states that MODER0 has offset 0, MODER1 has offset 2, MODER2 has offset 4 etc. like in the picture below:

enter image description here

If I try the following code:

GPIOB_device.GPIOB_MODER.R = 0xFFFF0000;

32 bits at address 0x40020400 look like this:

00000000 00000000 11111111 11111111

Why does that happen? Why are the bytes inverted?

Furthermore, if I try:

GPIOB_device.GPIOB_MODER.B.MODER0 = 0b10;

the register looks like this:

00000000 00000000 11111111 10111111

but it should look like this:

10000000 00000000 11111111 11111111

What am I doing wrong?

John
  • 39
  • 3
  • Where do you get the view you describe here "32 bits at address 0x40020400 look like this:"? Can you change the view settings to show blocks of 32bit? – Yunnosch Jul 16 '18 at 07:01
  • 1
    How did you print the content of those 32 bits? You might see typical little endian represenation of 0xFFFF0000 – Gerhardh Jul 16 '18 at 07:01
  • Try looking at `0x04030201`, what does that look like? – Yunnosch Jul 16 '18 at 07:03
  • 3
    C99 6.7.2.1-11:An implementation may allocate any addressable storage unit large enough to hold a bit- field. If enough space remains, a bit-field that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit. If insufficient space remains, whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is implementation-defined. The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined. The alignment of the addressable storage unit is unspecified. – ivand58 Jul 16 '18 at 07:07
  • 3
    I would suggest not writing "own implementations" before you understand the simple endianes problem.. BTW most beginners is trying to reinvent the wheel – 0___________ Jul 16 '18 at 07:42
  • 1
    Using structs across compile domains is a very bad practice, it is not expected to work or continue to work over time. Falls into "implementation defined" which means you compile it again someday in the future same or different compiler and no reason to expect it to work as desired. And then you have incorrectly assumed how a union works as well, double implementation defined. Where instead you could easily write some very portable code that boils down to the same instructions once compiled. – old_timer Jul 16 '18 at 10:57
  • 1
    Why it is like that is because it is implementation defined as to which end or where they pack the bits. Depending on the compiler and settings they can even choose to use 64 bit quantities as the smallest unit and align this data in that space. – old_timer Jul 16 '18 at 10:59
  • Take the bitfield portion off of R:32 make it just uint32_t R; That should not have bitswapped if used as a non-bitfield variable (nor byteswapped either) if a 32 bit store was used. The bits in the struct they are fair game regardless of endianness of the target. – old_timer Jul 16 '18 at 11:09
  • The icing on the cake here is that elsewhere in the spec it is made clear that uint32_t x; (or unsigned int) is not equal in size to uint32_t y:2; So the compiler sees a 2 bit quantity and is free to choose the transaction size so B.MODER0 = 0b10; (What compiler is this that supports 0b10?) could take your 0x0000FFFF register and turn it into say 0x8000FFFF, or 0x80ABCDEF or 0x80000000 or 0x80FFFFFF, etc. due to a combination of the compiler and the logic. – old_timer Jul 16 '18 at 11:56
  • if the spec says implementation defined then do not use that feature outside your compile domain if you use it at all. bitfields are perfectly fine within a project built by the same tool on the same day passing data or sharing data between functions in the same project with the same compiler settings same compiler same day. Ccrossing compile domains though even structures are fair game, much less bitfields. Unions cannot be used in this way its an either or, just because on your machine with your favorite compiler doesnt mean it is safe to use. – old_timer Jul 16 '18 at 11:59
  • You might be finding bitfields a proper way to represent hardware registers exactly until you find you need to port your program to another platform (compiler and/or hardware)... – tofro Jul 16 '18 at 12:49

2 Answers2

3

Your STM32F4 system is little-endian. All values are stored with the least-significant byte first. Therefore 0x12345678 is stored into memory as individual bytes 0x78 0x56 0x34 0x12, with address increasing left to right. Almost all current programming platforms are like this!

Bitfield layout is implementation-defined. However I'd assume that on your platform it is from lowest bit number to highest, not otherwise, therefore you'd need to reverse all of the MODER15 to MODER0.

Now, it would be actually cleaner if you didn't use a bitfield for this at all, opting to using bit masking and shifts instead - this is also because many times one wants to address the GPIO ports with the port number in a variable - and this isn't possible with bitfields, so you'd probably end up having both methods anyway.

  • 1
    I have noticed that many beginners when they learn about the bitfields, start rewriting the ARM headers :). – 0___________ Jul 16 '18 at 08:33
  • Sadly this "works on my machine" has lead to an epidemic of folks using this union/struct including in commercially available libraries and headers. As fewer and fewer compilers are used the implementation defined starts to feel like the standard, when it really isnt. So when they re-write them unfortunately there are too many other examples of this bug. And or the ghee whiz feeling of using cool things like unions and bitfields without actually understanding where it is safe to use them. – old_timer Jul 16 '18 at 11:06
1

Don't do this. Bit-fields are just too poorly defined in the standard to be useful. It is common that embedded compilers provide trashy register maps with structs just like the one you posted, but those structs are not portable between compilers, let alone different MCUs. Typically the compiler relies on non-standard extensions for such register maps, because portability to other compilers is usually not in the compiler vendor's interest.

Apart from a whole lot of issues with bit-fields and endianess, unions could also in theory contain padding (although very unlikely in this specific case).

Instead you should use the fully portable industry de facto standard way of accessing registers. Simply do:

#define GPIOB (*(volatile uint32_t*)0x40020400u)

GPIOB |=  MASK; // set bit
GPIOB &= ~MASK; // clear bit

alternatively

GPIOB |=  (1u << MASK);    // set bit
GPIOB &= ~(1u << MASK);  // clear bit

Either is fine, it's a matter of style. These are fully portable and also endianess-independent.

And note that the bit-field is pretty useless too, suppose you want to access the register bit by bit - then you can't use the bit-field anyhow:

for(uint32_t i=0; i<32; i++)
{
  do_something( GPIOB & (1u << i) );
}

As a side note 0b10 is also non-standard. You should avoid non-standard extensions whenever possible. (And besides, the whole reason hex is used is because binary is such a horrible format to read.)

Lundin
  • 195,001
  • 40
  • 254
  • 396