Accessing memory-mapped peripherals with Arm DS

Tutorial about accessing memory-mapped peripherals with Arm DS


Introduction Arm recommendations Alignment of registers Mapping of variables to specific addresses Code efficiency

Alignment of registers

If you want to map 16-bit registers on 32-bit alignment as recommended, then you could use:

  1. volatile unsigned short u16_IORegs[40];

    This code only allows accesses to even-numbered registers (each index in the array corresponds to 16-bits). You must double the register number. For example, to access the fourth register you could use index 8:

    
         x = u16_IORegs[8];
        u16_IORegs[8] = newval;
        
  2. volatile unsigned int u32_IORegs[20];

    The registers are accessed as 32-bit values. But a simple peripheral controller, like an Arm AMBA APB bridge, reads unused values into the top bits from signals that are not connected to the peripheral. In a little-endian system, the used values map to D31-D16. So, when such a peripheral is read, it must be cast to an unsigned short to get the compiler to discard the upper 16-bits. This type casting effect was the case shown in the previous example in this documentation. This example used a casting to read the peripheral registers and save their contents in the variables int_val, short_val, and char_val.

    For example, to access peripheral register 4:
    
        x = (unsigned short)u32_IORegs[4];
        u32_IORegs[4] = newval;
        
  3. Use a struct.

    Using a struct allows descriptive names to be used and can accommodate different peripheral register widths.

    Note: The padding is 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: The peripheral locations must not be accessed using:

    • __packed structs where unaligned members are allowed and there is no internal padding

      OR

    • C bitfields, for example, by defining the variable unsigned int isCorrect: 1.

    If the previous access methods are used, the compiler only uses 1-bit to store data, instead of 32-bits. This 1-bit data store is common when storing the data as a Boolean. However, if you use the previous methods, it is not possible to control the number and type of memory accesses the compiler performs. This can result in code that is non-portable, has undesirable side-effects, and does not work as intended. The recommended way of accessing peripherals is through explicit use of architecturally defined types such as int, short, and char on their natural alignments.

Previous Next