4
\$\begingroup\$

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.

\$\endgroup\$
  • 1
    \$\begingroup\$ You would have to pad the structure with enough unused bytes to fill the hole. \$\endgroup\$ – Majenko Oct 25 '14 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 Oct 25 '14 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 Oct 25 '14 at 19:51
2
\$\begingroup\$

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.

\$\endgroup\$
  • 2
    \$\begingroup\$ Note that to battle the compiler, you can introduce specific type attributes, e.g. in GCC __attribute__((packed)) \$\endgroup\$ – Dzarda Oct 31 '14 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 Oct 31 '14 at 14:49
  • \$\begingroup\$ Agreed. I just mentioned an alternative. \$\endgroup\$ – Dzarda Oct 31 '14 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 Nov 1 '14 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 Nov 3 '14 at 7:15
2
\$\begingroup\$

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.

\$\endgroup\$
  • \$\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\$ – Fizz Jan 8 '15 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\$ – Fizz Jan 8 '15 at 17:14

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.