Electronic – Problem in writing register maps using C structure for a TI Microcontroller

cmicrocontrollerregistertexas instruments

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.

Best Answer

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.