Splitting out a frame of bytes

Wouldn't that be an anonymous cast? AKA type conversion via pointer? Actually, the union seems more explicit.

uint32_t inData = 0xDEADBEEF;

struct fiddleBits* fp = (fiddleBits*)&inData;

A static cast would probably be cleaner, but I don't really care.

What about bit alignment of bit fields that straddle a byte/word boundary in a struct? I though it was not guaranteed. Isn't the compiler allowed to covertly control packing to suit its own whim? Sorry to be such a nuisance.

I think it will work if the bit fields are bit fields of an int32_t (int64_t).

And as far as I remember, bit fields are always packed.

FYI, OP corrected the size, said it was 32 bits...

That does not really matter.

As long as the packing is defined that way by the implementation. There is no guarantee in the language definition.

If you use bit fields of the same type as the to be analyzed data,
it should work on any implementation.

Microsoft is well known for making up their own rules. Check this:
https://stackoverflow.com/questions/1490092/c-c-force-bit-field-order-and-alignment

It works, I don't want to port it somewhere, so what?

It's just, if you recommend it to other people, you should send them into it with their eyes open.

I would prefer the non-portable bit field version over the #define << & version.
At least the compiler does not mess up bit masks.

I can see why. But the expanding bubble of Arduino core implementations means that more and more different compiler tool chains will come into use. I think it's safer to follow the language spec and forget about it almost forever. Even a humble hobbyist could now conceivably end up using several different cores... and it will only get worse (better!).

I might even use your suggestion somewhere, I like it... but with some comments attached. The nature of the code would influence me. If it's a one off "secret" project vs. a generic library or published project or something...

I think bit fields will work just like expected for some time to come,
if only to support legacy code - from the time when memory was expensive.

If you use the bit fields, it just shifts the burden of masking and shifting to the compiler.
It should work on any platform, as the base type for the fields and the combined data is the same.
Sure, there are limitations, but for the above simple dissecting it should work on any platform.

The readability of the bit field variant is much better, and the chance to make an error much lower.

Sure, but like I said, you should go in with your eyes open. The fact that it is specified as implementation dependent, means that even a single implementation might quietly pack different structs differently in different circumstances, and you could happily carry on for years before discovering it. So the fact that it works for you today, is not a complete test.

It's a different story if the compiler vendor exposes, documents and guarantees the implementation, then you can be sure. But I think this is not typically the case. The whole thing rests on how much certainty you need.

I think I see your point. We have meaningful names alright, but there are four of them for every chunk of bits we want to extract, and this doesn't scale well and leads to more confusion down the road. But my paper & pencil approach ends up being more concise than this. Let's do it my way:

1101 1110 1010 1101 1011 1110 1110 1111 // deadbeef
.... .... .... .... ..** ***. .... .... // we're after the stars
0000 0000 0000 0000 0011 1110 0000 0000 // so this is our mask

That mask's value is 15872 (or 0x3e00, which is easier to remember and harder to mistype). Of course the trailing zeros could be trailing ones or a mix of both, because we are going to discard them anyway when we shift bits, but this is tidier in my view. Our offset is 9 by definition (the dots to the right of the stars). Like Laurel and Hardy, the mask and the offset always go about together: what about putting them into a structure?

struct BitExtractor
{
  const uint32_t mask;
  const uint8_t  offset;
};

Now, we need a function that will do the extraction in one step:

uint32_t getBits (const uint32_t data, const struct BitExtractor b_e)
{
  return (data & b_e.mask) >> b_e.offset;
}

And we can christen our BitExtractor instances, each one with its own name:

const struct BitExtractor parameterAlfa    = {0x3e00, 9};
const struct BitExtractor parameterBravo   = {..., ...};
const struct BitExtractor parameterCharlie = ...;

Let's invoke it:

uint32_t result = getBits (0xdeadbeef, parameterAlfa);

And we find that result == 0x1f.

I've actually used a piece of paper and a pencil to work this out, but, of course, this can be done programmatically, once the procedure is clearly understood.

As a mechanical engineer by trade - this last post really clicks with me. I totally understand it now from the visual demonstration coupled with the explanation above. Thanks everyone, really great replies. I'll be bookmarking this one for future reference!

I like everything about this except the hard coded mask, you can tell from my previous posts that I am sold on calculating it. It would be clearer to be able to just define the data field length numerically, and let the software make the mask:

struct BitExtractor
{
  const uint8_t bitFieldLength;
  const uint8_t  offset;
};
...
const struct BitExtractor parameterAlfa    = {5, 9};
const struct BitExtractor parameterBravo   = {..., ...};
const struct BitExtractor parameterCharlie = ...;

The function getBits() can calculate a mask based on the math presented above.

I like the idea of a function that can calculate the mask - could you elaborate on how this would be achieved using getBits()? It works as is but if I can create a universal function that would be neater and I could keep it for future projects :slight_smile:

This is one way to do it:

uint32_t makeMask (uint8_t length, uint8_t offset)
{
    uint8_t     i = 0;
    uint32_t mask = 0;

    while (i < length) mask += 1 << i++;
    mask = mask << offset;

    return mask;
}

This is a little idiomatic, that's why i favour the paper & pencil approach to have a clearer understanding of what's going on. Of course what this function does can become a preliminary step in the new getBits(). This new function, then, could take a structure containing the length and the offset (but a simple 2-element uint8_t array would suffice in this case).

Note: I tried this on my PC; it is untested on an arduino.