0
\$\begingroup\$

I've moving over from 8 bit bare-metal registers, and having to learn some new C Kungfu to wrap my head around the CMSIS Core approach.

I have a snippet of code here from a Peripheral Access Layer from a ARM Cortex M vendor. They create this SN_WDT_TYPE structure, which you can use to set watch-dog timer registers using their notation.

Why do they use the unions? I haven't seen this kind of syntax before.

If you use unions to create structures like that, do you they go several layers deep with pointers? Memory management with the unions? Is there some C syntax thing I'm missing here?

This might be CMSIS specific, does anyone know what the ": 1" is doing in those struct declarations...? I know the __IO ties back to some CMSIS definition of read/write.

ARM'S CMSCIS PERIPHERAL NAMING CONVENTION -- This example code doesn't seem to confirm too gracefully...

/* ================================================================================ */
/* ================                     SN_WDT                     ================ */
/* ================================================================================ */


/**
  * @brief Watchdog Timer (SN_WDT)
  */

typedef struct {                                    /*!< SN_WDT Structure                                                      */

  union {
    __IO uint32_t  CFG;                             /*!< Offset:0x00 WDT Configuration Register                                */

    struct {
      __IO uint32_t  WDTEN      :  1;               /*!< WDT enable                                                            */
      __IO uint32_t  WDTIE      :  1;               /*!< WDT interrupt enable                                                  */
      __IO uint32_t  WDTINT     :  1;               /*!< WDT interrupt flag                                                    */
           uint32_t             : 13;
      __O  uint32_t  WDKEY      : 16;               /*!< Watchdog register key                                                 */
    } CFG_b;                                        /*!< BitSize                                                               */
  };

  union {
    __IO uint32_t  CLKSOURCE;                       /*!< Offset:0x04 WDT Clock Source Register                                 */

    struct {
      __IO uint32_t  CLKSOURCE  :  2;               /*!< WDT clock source                                                      */
           uint32_t             : 14;
      __O  uint32_t  WDKEY      : 16;               /*!< Watchdog register key                                                 */
    } CLKSOURCE_b;                                  /*!< BitSize                                                               */
  };

  union {
    __IO uint32_t  TC;                              /*!< Offset:0x08 WDT Timer Constant Register                               */

    struct {
      __IO uint32_t  TC         :  8;               /*!< Watchdog timer constant reload value                                  */
           uint32_t             :  8;
      __O  uint32_t  WDKEY      : 16;               /*!< Watchdog register key                                                 */
    } TC_b;                                         /*!< BitSize                                                               */
  };

  union {
    __O  uint32_t  FEED;                            /*!< Offset:0x0C WDT Feed Register                                         */

    struct {
      __O  uint32_t  FV         : 16;               /*!< Watchdog feed value                                                   */
      __O  uint32_t  WDKEY      : 16;               /*!< Watchdog register key                                                 */
    } FEED_b;                                       /*!< BitSize                                                               */
  };
} SN_WDT_Type;
\$\endgroup\$

closed as off-topic by old_timer, RoyC, Eugene Sh., Sparky256, DoxyLover Jan 21 '18 at 11:04

  • This question does not appear to be about electronics design within the scope defined in the help center.
If this question can be reworded to fit the rules in the help center, please edit the question.

  • 3
    \$\begingroup\$ This is a syntax for bitfields. Google them. And your initial post on StackOverflow was more appropriate. \$\endgroup\$ – Eugene Sh. Jan 15 '18 at 21:18
  • 2
    \$\begingroup\$ The unions allow you the choice of accessing the registers either as a whole (eg. SN_WDT_Type.CFG) or as individual bits/fields (eg. SN_WDT_Type.CFG_b.WDTEN). \$\endgroup\$ – brhans Jan 15 '18 at 21:25
  • 4
    \$\begingroup\$ I'm voting to close this question as off-topic because you shouldnt cross post questions, this is not an electrical engineer stack exchange question. \$\endgroup\$ – old_timer Jan 16 '18 at 1:16
  • 1
    \$\begingroup\$ bitfields are also elementary C, just read a C programming manual. \$\endgroup\$ – old_timer Jan 16 '18 at 1:19
  • 1
    \$\begingroup\$ @old_timer Bit-fields are not elementary C, they are barely covered at all by the C standard. Tell me, in the above code, which bit is the MSB? Turns out you can't tell, because it isn't specified anywhere. \$\endgroup\$ – Lundin Jan 16 '18 at 8:10
2
\$\begingroup\$

They use unions so that you can either access the register through a 32 bit access or through individual bits. This is unfortunately a very common way to declare register maps. Unfortunate because it relies on tons of poorly-specified behavior.

To begin with, unions and structs may contain padding anywhere, making them unsuitable for direct memory mapping. When using unions/structs for such purposes, you must always guard against accidental padding. Different systems have different alignment requirements and therefore different padding.

Then there is bit-fields, which is in itself is a language mechanism with little to no support by the C standard. Whenever using bit-fields, you rely on poorly-defined behavior or non-standard extensions.

All of this is very bad. You should never write code like the one posted. Preferably, bit mask macros and bit-wise operators should be used instead, as those are 100% standard, deterministic and portable.

\$\endgroup\$
2
\$\begingroup\$

this is what they are trying to do.

typedef union
{
    unsigned int reg;

    struct {
      unsigned int a:2;
      unsigned int b:14;
      unsigned int c:16;
    } bits;
} MYUN;

volatile MYUN *myun;


unsigned int fun1 ( unsigned int x, unsigned int y )
{
    myun=(volatile MYUN *)0x1000;
    myun->bits.b = x;
    myun->bits.c = y;
    return(myun->reg);
}

00000000 <fun1>:
   0:   e3a02a01    mov r2, #4096   ; 0x1000
   4:   e59f3034    ldr r3, [pc, #52]   ; 40 <fun1+0x40>
   8:   e5832000    str r2, [r3]
   c:   e59fc030    ldr r12, [pc, #48]  ; 44 <fun1+0x44>
  10:   e5923000    ldr r3, [r2]
  14:   e000000c    and r0, r0, r12
  18:   e1c3310c    bic r3, r3, r12, lsl #2
  1c:   e1833100    orr r3, r3, r0, lsl #2
  20:   e5823000    str r3, [r2]
  24:   e5923000    ldr r3, [r2]
  28:   e1a03803    lsl r3, r3, #16
  2c:   e1a03823    lsr r3, r3, #16
  30:   e1833801    orr r3, r3, r1, lsl #16
  34:   e5823000    str r3, [r2]
  38:   e5920000    ldr r0, [r2]
  3c:   e12fff1e    bx  lr
  40:   00000000    
  44:   00003fff    

address 0x40 will be filled in by the linker to hold the address where myun lives. You can see that r2 throughout is the address we specified for what this points at (pointing a union/structure across a compile domain at hardware, very bad idea in general with or without bitfields).

The current C/C++ standards attempt to insure that the bitfields will be linear they wont put the c bits between the a and b bits. But what is implementation defined, meaning whoever writes the compiler or backend can do whatever they want, is whether the a bits are the msbits or the lsbits likewise the c bits which half of that 32 bit word (assuming the size of int here to simplify the example, easily confirmed). Unions are in theory to share memory between the elements but they make it quite clear that that doesnt mean they are aligned. there is some verbage but it is clear that a two bit unsigned int and a full sized unsigned int are not the same type so dont have to line up, they make it clear that whatever after that doesnt have to line up. padding in structs is implementation defined as well making it a bad idea to use them across compile domains.

We see that to write the b bits they zero bits [15:2] 14 bits as defined, then apply the x bits there, then save as this is volatile.

Then read 0x1000 again, and mask and apply the y bits to [31:16] of the destination.

lastly returning what was read at 0x1000 as a whole 32 bit value.

The bitfields are a lazy way to avoid masking and shifting but are not reliable, where masking and shifting wont fail unless the compiler is grossly broken.

The union trick is another form of laziness to be able to see or use the whole value as one (32 bit) variable rather than the parts.

This is ghee whiz C language tricks to save on typing perhaps, at a huge risk. A habit like this if you continue to do it will fail. Now some folks may choose to do this as a form of job security, code that fails periodically that needs maintenance rather than write it once and done. Usually only works if management and co-workers dont know what you are up to.

It is a works on my machine thing, your machine your compiler on a particular day against a particular target. The same compiler or family against a different target may flip the bit field definitions, and pointing unions or structs against memory as a habit will fail eventually due to padding or alignment.

Masking and shifting is not implementation defined and doesnt vary from compiler to compiler or target to target (for non-buggy compilers).

At the end of the day both approaches generate masking and shifting, so choosing the one that is reliable over the one that is not would be wise.

\$\endgroup\$

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