I am currently busy adapting to the Cortex processor family, and I am amazed how ancient some of the coding practices have remained. A very typical case in point are the device header files used. Let us take as an example part of the definition of the interface to a DMA controller.
The relevant definitions for the destination width field look like this. First there is a normal struct definition without any structure:
1 2 3 4 5 6 7 |
typedef struct { __IO void * __IO SRCEND; /**< DMA source address end */ __IO void * __IO DSTEND; /**< DMA destination address end */ __IO uint32_t CTRL; /**< DMA control register */ __IO uint32_t USER; /**< DMA padding register, available for user */ } DMA_DESCRIPTOR_TypeDef; /** @} */ |
and the definition of the width field is all done with macros for manual assembly:
1 2 3 4 5 6 7 8 9 10 |
#define _DMA_CTRL_DST_INC_MASK 0xC0000000UL #define _DMA_CTRL_DST_INC_SHIFT 30 #define _DMA_CTRL_DST_INC_BYTE 0x00 #define _DMA_CTRL_DST_INC_HALFWORD 0x01 #define _DMA_CTRL_DST_INC_WORD 0x02 #define _DMA_CTRL_DST_INC_NONE 0x03 #define DMA_CTRL_DST_INC_BYTE 0x00000000UL #define DMA_CTRL_DST_INC_HALFWORD 0x40000000UL #define DMA_CTRL_DST_INC_WORD 0x80000000UL #define DMA_CTRL_DST_INC_NONE 0xC0000000UL |
Normal people would define this in proper C++, using something like
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
union DMA_Control { enum DestinationIncrement { DI_Byte, DI_HalfWord, DI_Word, DI_None, }; unsigned int word; struct Details { int : 30; DestinationIncrement destination_increment : 2; } d; }; struct DMA_Descriptor { __IO void * __IO SourceEnd; /**< DMA source address end */ __IO void * __IO DestinationEnd; /**< DMA destination address end */ __IO DMA_Control Control; /**< DMA control register */ __IO uint32_t _User; /**< DMA padding register, available for user */ }; |
Unfortunately, you are basically forced to use these ancient methods, because the compilers, even the highly regarded one from Keil, do not properly optimize when you are changing multiple fields in such a struct. If one updates several fields with constants, the compiler strangely enough does not optimize them. For example, if you would update a byte:
1 2 3 4 5 6 7 8 |
DMA_Descriptor desc; DMA_Control ctrl; ctrl = desc.Control; ctrl.d.source_size = DMA_Control::SW_Word; ctrl.d.source_increment = DMA_Control::SI_Word; ctrl.d.destination_size = DMA_Control::DW_Word; ctrl.d.destination_increment = DMA_Control::DI_None; desc.Control = ctrl; |
then the compiler would even on highest optimization add the constants individually, even though there is no volatile
forcing him to do so. And to top it off, the debugger is not even able to read enumerations in the bit field union, even though it can handle standard enum
s and plain int
bitfields just fine.
This is really too bad, because using bitfields properly would allow a good editor to make completion suggestions for the relevant enum
, and it would automatically catch any assignment errors.