I am writing register maps in C for a TI ARM based Micro-controller board. Here is the link to the datasheet.
I am using the following guidelines on how Register maps should be written in C: Register Maps. These guidelines are similar to ARMs CMSIS (Cortex Microcontroller Software Interface Standard) for writing C code.
I am facing problem with writing System Control Register maps(refer section 5.5 page 237 onwards in datasheet) using C struct. All registers are of size 32-bits
- The base address is 0x400F.E000.
- Register 1: Device Identification 0(DID0), offset 0x000
- Register 2: Device Identification 1 (DID1), offset 0x004
- Register 3: Brown-Out Reset Control (PBORCTL), offset 0x030
- Register 4: Raw Interrupt Status (RIS), offset 0x050
- etc..
If I now write a structure like this:
typedef struct
{
uint32_t DID0; // offset 0x000. distance: 0
uint32_t DID1; // offset 0x004. distance: 4
uint32_t PBORCTL; // **HOW to place this at offset 0x030 ?**
uint32_t RIS; // **HOW to place this at offset 0x050 ?**
// ...and so on
}PER_REG_MAP_T;
#define PERIPH_BASE ((uint32_t)0x400FE000)
#define MY_PERIPH_A ((PER_REG_MAP_T*)PERIPH_BASE)
void Reset(PER_REG_MAP_T* PERIPH_A)
{
PERIPH_A->DID0 = 0;
PERIPH_A->DID1= 0;
PERIPH_A->PBORCTL= 1;
PERIPH_A->RIS= 0;
// .. and so on
}
The main issue I am facing is how to place PBORCTL and RIS inside the structure as they are at offset 0x030 and 0x050 with respect to the base address of the structure. I have not done too much bit-level C-programming before so this question might be too simple, but I donot know how to do it.
-
1\$\begingroup\$ You would have to pad the structure with enough unused bytes to fill the hole. \$\endgroup\$Majenko– Majenko2014年10月25日 14:59:55 +00:00Commented Oct 25, 2014 at 14:59
-
\$\begingroup\$ Usually along with the datasheet for the uC itself it's helpful to have a copy of the compiler user guide and to spend some time crawling through the header files the vendor provides for the compiler specific to this chip. They usually provide predefined macros (#defines) and other compiler specific constructs that define how to access all the system config registers. It looks like what you're writing might already be provided by the vendor in a header file? \$\endgroup\$mixed_signal_old– mixed_signal_old2014年10月25日 18:41:56 +00:00Commented Oct 25, 2014 at 18:41
-
\$\begingroup\$ @mixed_signal_old: Yes there are already register mappings defined inside <device>.h file but problem is that the register definitions seems valid for some older devices and so not usable for my current device(doesn't match exactly the current device datasheet I have). I can adapt it to my current device but I rather write from scratch \$\endgroup\$nurabha– nurabha2014年10月25日 19:51:39 +00:00Commented Oct 25, 2014 at 19:51
2 Answers 2
Structures are unsuitable for writing register maps, because a struct can have padding bytes added anywhere inside it, for alignment purposes. This depends on the compiler - you have to ensure that no padding is used (for example through a static assert).
Furthermore, it is easy to make mistakes when writing large structures that are supposed to reflect a register map, you have to get every single byte right or you'll break everything. This makes the struct vulnerable during maintenance.
I would strongly suggest you to write register maps through macros, such as:
#define PERIPH_A_DID0 (*(volatile uint32_t*)0x400FE000u))
#define PERIPH_A_DID1 (*(volatile uint32_t*)0x400FE004u))
This also has the advantage of being 100% portable to any C compiler.
Alternatively, you could do something more intricate such as this:
typedef volatile uint8_t* SCI_port;
#ifndef SCI0
#define SCI0 (&SCI0BDH)
#define SCI1 (&SCI1BDH)
#endif
#define SCIBDH(x) (*((SCI_port)x + 0x00)) /* SCI Baud Rate Register High */
#define SCIBDL(x) (*((SCI_port)x + 0x01)) /* SCI Baud Rate Register Low */
#define SCICR1(x) (*((SCI_port)x + 0x02)) /* SCI Control Register1 */
#define SCICR2(x) (*((SCI_port)x + 0x03)) /* SCI Control Register 2 */
#define SCISR1(x) (*((SCI_port)x + 0x04)) /* SCI Status Register 1 */
#define SCISR2(x) (*((SCI_port)x + 0x05)) /* SCI Status Register 2 */
#define SCIDRH(x) (*((SCI_port)x + 0x06)) /* SCI Data Register High */
#define SCIDRL(x) (*((SCI_port)x + 0x07)) /* SCI Data Register Low */
Then when writing your driver, you can do like this:
void sci_init (SCI_port port, ...)
{
SCICR1(port) = THIS | THAT;
SCICR2(port) = SOMETHING_ELSE;
...
}
This is very useful when you have many identical peripherals on the MCU, with the same registers, but only want one code to control them.
-
2\$\begingroup\$ Note that to battle the compiler, you can introduce specific type attributes, e.g. in GCC
__attribute__((packed))
\$\endgroup\$Dzarda– Dzarda2014年10月31日 14:29:07 +00:00Commented Oct 31, 2014 at 14:29 -
\$\begingroup\$ @Dzarda It is better to use static asserts for that, since they are portable. For example
static_assert(sizeof(mystruct) == sizeof(mystruct.obj1) + sizeofmystruct.obj2) + ..., "Struct padding detected");
\$\endgroup\$Lundin– Lundin2014年10月31日 14:49:39 +00:00Commented Oct 31, 2014 at 14:49 -
\$\begingroup\$ Agreed. I just mentioned an alternative. \$\endgroup\$Dzarda– Dzarda2014年10月31日 15:18:43 +00:00Commented Oct 31, 2014 at 15:18
-
1\$\begingroup\$ @Lundin: I understand your point about packing. But the C-compiler will add padding according to the target architecture(ISA decided alignment requirement not the compiler). For example LLVM, gcc or intel C compiler should not change the way structure is packed for a specific ISA say ARM. The way structure is padded would change from one ISA to another ISA because of different alignment requirements in two different ISA. Second: What if I use uint8_t, uint16_t, uint_32t etc. types to make sure the structure is packed properly then the issue with unknown padding doesn't arise ? \$\endgroup\$nurabha– nurabha2014年11月01日 14:30:28 +00:00Commented Nov 1, 2014 at 14:30
-
\$\begingroup\$ @nurabha All registers in a register maps are not necessarily of the same width as the alignment requirement. So if you write your register map as a struct of lets say uint32_t, and 999 out of 1000 MCU registers are 32 bit but one is 16 bit... then your whole register map will be broken if you implement it as a struct and the compiler adds padding bytes. \$\endgroup\$Lundin– Lundin2014年11月03日 07:15:22 +00:00Commented Nov 3, 2014 at 7:15
Firstly ARM recommends using __packed
to get the minimum packing. Second, if you really need big gaps in your struct (and these can't be two structs for some reason) you can just put something like a char[30] reserved1
array in between and so forth for the other big gaps. Make sure you put the right sizes for these. You could double check with an assert that the next field's offset really is where you think; offsetof() will give you the offset of a field.
I wrote this a bit of a hurry, if you need more details, ask below and I'll expand (although not for a number of hours.)
By the way, it looks like TI has already written the header file in question for you. They seem to like using #define
for everything.
-
\$\begingroup\$ By the way, as I suspected, the typical practice at a big company like TI, which has hundreds if not thousands of uC models in their portfolio, is almost certainly to have that C header file generated from some master HDL file (or similar file). Googling a bit there sure are EDA software vendors that boast such a feature. TI probably uses something similar, possibly developed in-house given how big they are. \$\endgroup\$got trolled too much this week– got trolled too much this week2015年01月08日 17:06:16 +00:00Commented Jan 8, 2015 at 17:06
-
\$\begingroup\$ There is also a book that touches on the matter of HDL/C-header/documentation synchronization for register maps, from the perspective of IC manufacturers. \$\endgroup\$got trolled too much this week– got trolled too much this week2015年01月08日 17:14:33 +00:00Commented Jan 8, 2015 at 17:14
Explore related questions
See similar questions with these tags.