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.
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.
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.