What does ((Port *)0x41004400UL) mean here?

2019-03-04 20:13发布

问题:

I'm working on a developing board that has a 32-bit ARM based microntroller on it (namely the board is Atmel SAM D21J18A). I'm still at the learning phase and I have a lot to go, but I'm really into embedded systems.

I have some background in C. However, it's obviously not enough. I was looking at the codes of an example project by Atmel, and I didn't really get some parts of it. Here is one of them:

    #define PORT              ((Port     *)0x41004400UL) /**< \brief (PORT) APB Base Address */

Port is defined as:

    typedef struct {
        PortGroup             Group[2];    /**< \brief Offset: 0x00 PortGroup groups [GROUPS] */
    } Port;

and PortGroup is defined as:

typedef struct {
    __IO PORT_DIR_Type             DIR;         /**< \brief Offset: 0x00 (R/W 32) Data Direction */
    __IO PORT_DIRCLR_Type          DIRCLR;      /**< \brief Offset: 0x04 (R/W 32) Data Direction Clear */
    __IO PORT_DIRSET_Type          DIRSET;      /**< \brief Offset: 0x08 (R/W 32) Data Direction Set */
    __IO PORT_DIRTGL_Type          DIRTGL;      /**< \brief Offset: 0x0C (R/W 32) Data Direction Toggle */
    __IO PORT_OUT_Type             OUT;         /**< \brief Offset: 0x10 (R/W 32) Data Output Value */
    __IO PORT_OUTCLR_Type          OUTCLR;      /**< \brief Offset: 0x14 (R/W 32) Data Output Value Clear */
    __IO PORT_OUTSET_Type          OUTSET;      /**< \brief Offset: 0x18 (R/W 32) Data Output Value Set */
    __IO PORT_OUTTGL_Type          OUTTGL;      /**< \brief Offset: 0x1C (R/W 32) Data Output Value Toggle */
    __I  PORT_IN_Type              IN;          /**< \brief Offset: 0x20 (R/  32) Data Input Value */
    __IO PORT_CTRL_Type            CTRL;        /**< \brief Offset: 0x24 (R/W 32) Control */
    __O  PORT_WRCONFIG_Type        WRCONFIG;    /**< \brief Offset: 0x28 ( /W 32) Write Configuration */
    RoReg8                         Reserved1[0x4];
    __IO PORT_PMUX_Type            PMUX[16];    /**< \brief Offset: 0x30 (R/W  8) Peripheral Multiplexing n */
    __IO PORT_PINCFG_Type          PINCFG[32];  /**< \brief Offset: 0x40 (R/W  8) Pin Configuration n */
    RoReg8                         Reserved2[0x20];
} PortGroup;

So here, we are looking at the address 0x41004400UL, get the data in there, and then what happens?

I looked up for this but couldn't find anything useful. If you have any suggestions (tutorials, books etc.), please let me hear.

回答1:

Generally you can access a hardware register in C in this manner:

#define PORT  (*(volatile uint8_t*)0x1234)
  • 0x1234 is the register address
  • uint8_t is the type of the register, in this case 1 byte large.
  • volatile is required so that the compiler knows it cannot optimize such a variable, but that each read or write to the variable stated in the code must actually be done.
  • (volatile uint8_t*) casts the integer literal to an address of the desired type.
  • The left-most * then take the contents of that address, so that the macro can be used just as if PORT was a regular variable.

Note that this does not allocate anything! It just assumes that there is a hardware register present at the given address, which can be accessed by the type specified (uint8_t).

Using the same method you can also have other C data types to correspond directly hardware registers. For example by using a handy struct, you can map the whole register area of a particular hardware peripheral. Such code is however a bit dangerous and questionable, since it must take things like alignment/struct padding and aliasing in account.


As for the specific code in your example, it is a typical awful register map for a particular hardware peripheral (looks like a plain general-purpose I/O port) on a certain microcontroller. One such beast is typically provided with each compiler supporting the MCU.

Such register maps are sadly always written in awful, completely non-portable ways. For example, two underscores __ is a forbidden identifier in C. Neither the compiler nor the programmer is allowed to declare such identifiers (7.1.3).

What's really strange is that they have omitted the volatile keyword. This means that you have one of these scenarios here:

  • The volatile keyword is hidden beneath the Port definition. Most likely this is the case, or
  • The register map is full of fatal bugs, or
  • The compiler is such an awful piece of crap that it doesn't optimize variables at all. Which would make the issues with volatile go away.

I would investigate this further.

As for struct padding and aliasing, the compiler vendor has likely implicitly assumed that only their compiler is to be used. They have no interest in providing you with a portable register map, so that you can switch the the competitor's compiler for the same MCU.



回答2:

Nothing happens, because you only present some declarations. I'm not entirely sure what the question actually is, but to briefly explain that code:

  • 0x41004400UL is obviously an address in I/O space (not regular memory) where a port starts (a set of I/O registers)

  • This Port consists of two groups with a similar arrangement of single registers

  • struct PortGroup models these registers exactly in the layout present on the hardware

To know the meaning of the Registers, look up the hardware documentation.