| |||
| Home > Writing Code for ROM > Using scatter loading with memory-mapped I/O > Using arrays or structs | |||
The following examples show how to use arrays or structs to access peripheral registers.
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).
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;
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.
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;