1
\$\begingroup\$

So I've designed a piece of hardware that has an Atmega168pa microcontroller communicating with an external IC over an SPI bus. The external device has a few registers, with hardware mapped bits.

I'm looking for a way to easily manipulate this structured-bit data in the Atmega. The first obvious solution is a bit-field, but I haven't had much success.

enter image description here

Goal is to try to map this into 3 bytes so that I can:
A) Access the data simply, e.g. n_reg.b_counter = 230
B) Write this structured data byte by byte with an spi_write(uint_8t) type function. Obviously this would have to iterate through the three bytes, maybe with some kind of index.

What I've tried thus far:

struct PLL_N_REG 
{
    uint32_t control_bits : 2;
    uint32_t a_counter : 5;
    uint32_t reserved : 1;
    uint32_t b_counter : 13;
    uint32_t cp_gain : 1;
    uint32_t div_by_2 : 1;
    uint32_t div_by_sel : 1;
};

struct PLL_N_REG n_reg;
n_reg.div_by_sel = 1;
n_reg.b_counter = 230;
n_reg.control_bits = 2;

This fulfills objective A as it's easy to access, but I don't know how I'd easily grab each byte in order to send to the spi_write(uint8_t) function.

I've tried doing something similar with hardware projects in the past but have ended up using work-arounds. I'd like a method I can apply easily to any hardware I need to implement. All of the other examples I've found have been for mapping microcontroller-internal hardware registers.

Thanks a lot in advance!

\$\endgroup\$
  • 3
    \$\begingroup\$ You mean like with a union? \$\endgroup\$ – Ignacio Vazquez-Abrams Jul 23 '16 at 17:56
  • \$\begingroup\$ @IgnacioVazquez-Abrams No. \$\endgroup\$ – RabidAlpaca Jul 24 '16 at 8:20
3
\$\begingroup\$

Unfortunately, the C standard does not guarantee your struct is even the same size as a uint32_t since there's no requirement that bit-fields be compactly stored. Even if they were, you still have to figure out which of the 32 bits are you interested in. The first 24? Last 24? somewhere in the middle (unlikely, but possible)? This is not portable.

If you are ok with the performance hit, you can write a helper function which will pack the constituent components for you.

uint32_t pll_n_reg_pack(PLL_N_REG field)
{
    uint32_t res = n_reg.control_bits;
    res |= n_reg.a_counter << 2;
    res |= n_reg.reserved << 7;
    res |= n_reg.b_counter << 8;
    res |= n_reg.cp_gain << 21;
    res |= n_reg.div_by_2 << 22;
    res |= n_reg.div_by_sel << 23;
    return res;
}

If performance is critical and the compiler isn't able to optimize this, you might want to consider working with a raw uint32_t (or uint8_t[3]) and localize the code such that the bit shifting/masking is limited.

More useful information:

\$\endgroup\$
  • \$\begingroup\$ If you make the function inline, you may be surprised how good optimizers have become. \$\endgroup\$ – Simon Richter Jul 23 '16 at 23:25
  • \$\begingroup\$ @SimonRichter true, optimizers have come a long way. However, I am also constantly surprised at how many times the optimizer fails to make very "simple" transforms. I would certainly test it because bitfield structs are much nicer to work with than raw bit masks/shifting. Also, even if it does end up with a performance hit, I would also test to see if that even matters, since sometimes it doesn't (how many times do you need to update the PLL settings anyways?). \$\endgroup\$ – helloworld922 Jul 24 '16 at 3:17
  • 1
    \$\begingroup\$ "This is not portable.". Sometimes you have to step back and ask yourself if a routine to manipulate the PLL settings on a specific CPU has to be portable to other architectures. If I read the standard correctly, this is all implementation defined, that is, not undefined behavior. It has to be implemented a specific way, the compiler manual must inform about this, and then it is perfectly fine to follow that documentation. \$\endgroup\$ – pipe Jul 24 '16 at 3:48
  • \$\begingroup\$ Well, if you're sending SPI commands to configure the PLL, presumably the PLL is on a separate chip from the MCU (even specified in the OP's question in this instance), and it would be useful to write code which you can use multiple times if you want to use the same PLL module with multiple MCU's. \$\endgroup\$ – helloworld922 Jul 24 '16 at 4:47
  • \$\begingroup\$ And yes, writing compiler/MCU specific code which requires implementation defined behavior is perfectly fine, but I would avoid it unless there's a really good reason not to. \$\endgroup\$ – helloworld922 Jul 24 '16 at 4:50
1
\$\begingroup\$

You can cast the address of your structure object to a uint8_t * like this.

struct PLL_N_REG n_reg;
uint8_t * ptr = (uint8_t*)&n_reg;

If spi_write() really takes a uint8_t then call it like this.

int byte;
for (byte = 0; byte < 3; byte++)
{
    spi_write(*ptr);
    ptr++;
}

Or if spi_write() takes a uint8_t* instead then call it like this.

int byte;
for (byte = 0; byte < 3; byte++)
{
    spi_write(ptr);
    ptr++;
}
\$\endgroup\$
  • 1
    \$\begingroup\$ This is not guaranteed to work or be portable, even if it will compile. See my answer for why (basically C does not guarantee the layout of a bitwise struct). \$\endgroup\$ – helloworld922 Jul 24 '16 at 3:19
  • \$\begingroup\$ I agree that the bitfield is not 100% portable. But I suspect it works like the questioner expects on the vast majority of systems or there. The cast operation I've used here should be instantly recognizable and understood by any experienced C programmer. I believe this is a case where the simple solution that works good enough is actually good enough. \$\endgroup\$ – kkrambo Jul 24 '16 at 17:27
  • \$\begingroup\$ It's something to keep in mind, though, when you start to wonder "why is my PLL being configured wrong?" because you didn't account for the endianness of your target architecture. The solution which gets it correct is basically the same length, and with a good optimizer probably has a negligible performance hit. \$\endgroup\$ – helloworld922 Jul 24 '16 at 18:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.