6.9.3. Using arrays or structs

The following examples show how to use arrays or structs to access peripheral registers.

Using an array of shorts

To access some 16-bit peripheral registers on 16-bit alignment, you can write:

volatile unsigned short u16_IORegs[20];

For little-endian systems, this works if your peripheral controller can route the peripheral databus to the high part (D31..D16) of the ARM databus as well as the low part (D15..D0) depending on the address that you are accessing. You must check if this multiplexing logic exists in your design (the standard ARM APB bridge does not support this).

Using a struct

The advantages of using a struct over an array are:

  • descriptive names can be used (more maintainable and legible)

  • different register widths can be accommodated.

Padding should be made explicit rather than relying on automatic padding added by the compiler, for example:

struct PortRegs {
  unsigned short ctrlreg;  /* offset 0 */
  unsigned short dummy1;
  unsigned short datareg;  /* offset 4 */
  unsigned short dummy2;
  unsigned int data32reg;  /* offset 8 */
} iospace;
x = iospace.ctrlreg;
iospace.ctrlreg = newval;

Note

Peripheral locations should not be accessed using __packed structs (where unaligned members are allowed and there is no internal padding), or using C bitfields. This is because it is not possible to control the number and type of memory access that is being performed by the compiler.

The result is code that is non-portable, has undesirable side effects, and will not work as intended. The recommended way of accessing peripherals is through explicit use of architecturally-defined types such as int, short, char on their natural alignment.

Using a pointer to struct/array

struct PortRegs {
  unsigned short ctrlreg;  /* offset 0 */
  unsigned short dummy1;
  unsigned short datareg;  /* offset 4 */
  unsigned short dummy2;
  unsigned int data32reg;  /* offset 8 */
};
volatile struct PortRegs *iospace = 
                (struct PortRegs *)0x40000000;
x = iospace->ctrlreg;
iospace->ctrlreg = newval;

The pointer can be either local or global. If you want the pointer to be global in order to avoid the base pointer being reloaded after function calls, make iospace a constant pointer to the struct by changing its definition to:

volatile struct PortRegs * const iospace =
    (struct PortRegs *)0x40000000;
Copyright © 1999-2001 ARM Limited. All rights reserved.ARM DUI 0056D
Non-Confidential