Heh. I just posted a long response on the same subject on another forum (although, with an Atmel SAMD flavor.)
Here it is. It sounds like you particularly need to study that first reference:
Representing and Manipulating Hardware in Standard C and C++ A nice part about this is that since ARM "standardized" the general style in CMSIS, what you learn will be applicable to other ARM chips as well. (And elsewhere. The new AVR Mega-0, Tiny-0, and Tiny-1 series are moving to this sort of structure-based peripheral definition.)-
The definitions follow CMSIS conventions.
CMSIS is an ARM standard, the most important parts of which are:
- Peripheral registers are accessed as memory (ARM doesn't have any separate "peripheral" instructions, so everything is located somewhere inside the 32bit address space.)
- The memory space for each particular peripheral is described by a C "structure" whose start address is at the beginning peripheral. This is a very common technique for accesses memory-mapped peripherals, but may seem strange if you haven't seen it before. See http://www.open-std.org/jtc1/sc22/wg21/docs/ESC_SF_02_465_paper.pdf for example. The newer AVR chips are also setting things up like this.
- ARM "standard" peripherals get names and content defined by ARM. This is things like the Systick timer and the Interrupt Controller.
- Individual vendors have a lot of latitude on the details of their vendor-specific peripherals. Atmel has the convention that each peripheral register has a .reg and .bits union for each register - use the .reg to access the "full" register at once, use .bits to access individual bitfields within the register. (accessing bitfields can be somewhat prone to unexpected behavior, since the chip itself will need to access full registers.) Sometimes vendors do a poor (IMO) job.
All this usually involves some pretty ugly C code that is somewhat compiler dependent and causes Language experts to groan, but in general end users don't need to worry about that - the necessary include files for a particular compiler are provided, and they'll work.
-
So for
PORT->Group[0].DIR.reg:
PORT is where all of the SAMD gpio ports start, defined in
...tools/CMSIS-Atmel/1.2.0/CMSIS/Device/ATMEL/samd51/include/samd51j19a.h:
#define PORT ((Port *)0x41008000UL) /**< \brief (PORT) APB Base Address */
-
They're evenly spaced, and implemented as an array of PortGroup members (one for each of PORTA, etc), in
.../tools/CMSIS-Atmel/1.2.0/CMSIS/Device/ATMEL/samd51/include/component/port.h:
typedef struct {
PortGroup Group[4]; /**< \brief Offset: 0x00 PortGroup groups [GROUPS] */
} Port;
-
(Yeah:
PORT is actually 4 ports, a single port is called a
PortGroup, and what's in the datasheet as PORTA should be referred to as
PORT.Group[0]. That's the sort of thing I meant by "Poor descisions.")
PortGroup is defined (also in
port.h) with the individual registers:
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 */
__IO PORT_EVCTRL_Type EVCTRL; /**< \brief Offset: 0x2C (R/W 32) Event Input Control */
__IO PORT_PMUX_Type PMUX[16]; /**< \brief Offset: 0x30 (R/W 8) Peripheral Multiplexing */
__IO PORT_PINCFG_Type PINCFG[32]; /**< \brief Offset: 0x40 (R/W 8) Pin Configuration */
RoReg8 Reserved1[0x20];
} PortGroup;
-
And then each register is also defined in port.h (the __IO vs __I is elsewhere, though.)
-
Also note the comparatively huge number of individual registers assigned to each peripheral. And the unallocated space ("Reserved") that makes the arrays come out even. You can get pretty creative when you have a couple of gigabytes of address space to throw at things...
-
This all works pretty well, in the end. Note that the compiler will normally optimize away what looks like it might be inefficient ("array accesses just to find the port??"), so the resulting binary code is about as good as it could be if you did it any other way...