Accessing SAM MCU Registers in C
This page will show you how to access SAM MCU Peripheral registers and bit fields in C, without the use of any framework, such as Advanced Software Framework (ASF3) or START. Additionally, you will learn how to access the SAM CPU-based peripherals using the Arm® Cortex® Microcontroller Software Interface Standard (CMSIS) core Application Programming Interfaces (APIs), which are also included as part of the standard SAM MCU compiler toolchain in Atmel® Studio.
Summary
For standard MCU peripherals, such as Universal Asynchronous Receiver Transmitter (UART), Serial Peripheral Interface (SPI), Analog-to-Digital Converter (ADC), etc., the compiler toolchain contains specially developed Device-Specific, Instance, and Component header files that simplify coding for direct and indirect access of the registers and bitfields in these hardware modules.
For CPU-based peripherals, such as the Nested Vectored Interrupt Controller (NVIC) or the System Timer, APIs from the Cortex CMSIS are used for configuration. The APIs are found in the following header files:
- core_cm0.h - contains processor register definitions
- core_cm0plus.h - contains definitions for interrupt controller, system tick timer, and others
- core_cmFunc.h - contains access functions for access to core registers (PSP, MSP, etc…)
The header files mentioned above will appear in your projects' Dependencies folder in Atmel Studio upon a successful project build.
Use of C Pointers for Hardware Mapped Registers
Pointers are a critical facility provided by the C language that makes it one of the most preferred languages in embedded programming. Technically, C is known as a middle-level language since it provides many of the higher language control structures but also allows simple and direct control of the hardware. It does this through the concept of the hardware pointer. Basic control of not just the core process but of all the peripheral blocks in a microcontroller is presented in sets of registers that are realized as special memory locations mapped directly into the address space of the processor being used.
Direct Peripheral Register Access
The PORT Group 0 GPIO controller for the SAM D21 microcontroller is located at address 0x41004400. Starting from this memory address there are registers that are specifically designed to control the PORT Group 0 functions. The following extract from the SAM D21 datasheet documents the first three registers of the PORT Group 0 peripheral.
For reasons partially related to the fact that the SAMD21 is a 32-bit microcontroller, many of the registers in this peripheral are 32-bits wide. Thus the first register in the set, DIR, spans the address range 0x41004400 - 0x41004403. The SAM D21 is a little endian processor, so the LSB is located at address 0x41004400.
To define a pointer to this specific memory address we can do the following:
PORT0_DIR_ptr = (unsigned int *)(0x41004400);
A value can now be written or read from this memory location.
port0_value = *PORT0_DIR_ptr;
// write Bit 23, Bit 13 and Bit 4 as 1 in the PORT0_DIR
*PORT_DIR_ptr = (1 << 23) | (1 << 13) | (1 <<4);
The entire peripheral register set can be mapped in the header files using symbolic names for the addresses to make the code more readable.
#define REG_PORT_DIRCLR0 (0x41004404U) /* (PORT) Data Direction Clear Register 0 */
#define REG_PORT_DIRSET0 (0x41004408U) /* (PORT) Data Direction Set Register 0 */
#define REG_PORT_DIRTGL0 (0x4100440CU) /* (PORT) Data Direction Toggle Register 0 */
<and so on>
The instance header file type is used to define SAM D21 peripheral registers in this way.
Instance Header Files
These header files are used to associate register name identifiers to SAM D21 memory addresses to allow convenient program access.
Here is an example snippet from a port.h SAM D21 instance header file:
- RwReg signifies a read and write 32-bit register.
- RoReg signifies a read-only 32-bit register.
- WoReg signifies a write-only 32-bit register.
Suffixes may be added to indicate a smaller register size, for example:
- RwReg8 signifies a read and write 8-bit register
- RwReg16 signifies a read and write 16-bit register
Example
Using a port register macro from the ports.h instance header file, the following code example sets the PORT group 0, bit 30 pin high (port pin PA30).
Or using the available pin mask macros in the samd21j18a.h pio header file, the same code can be written as:
Indirect Peripheral Register/Bit-Field Access
There is another more elegant way of defining register sets that encapsulates the definition of the registers as a more complete object. A structure can be defined that clones the structure of the register set and then the head of the structure can then be assigned the starting (or base) address of the peripheral set. Since microcontrollers generally have more than one of a specific peripheral available, this allows very easy access to multiple peripherals sets with only the change in the starting address for each instance that is created.
Device-Specific Header Files
The device-specific header file (samd21j18a.h) contains the base addresses for all peripherals and components. The base address is used as a pointer to each set of peripheral and component register groups.
The following excerpt shows the base address definition of the Generic Clock Controller Module (GCLK) in the ATSAMD21J18A MCU.
A port IO (pio) header file with the same name (samd21j18a.h, located in a different directory) contains useful port pin macro definitions.
#define PORT_PA30 (_UL(1) << 30) /**< \brief PORT Mask for PA30 */
Component Header Files
Component header files are used to define the structure of a register set associated with a peripheral. For example, the GCLK register set is defined by the following structure type in the gclk.h component header file.
Individual peripheral register types and bitfields are also defined using unions and structures of members. The following union type defines register and bit-field level access for the GCLK CLKCTRL register.
There are several ways to access the CLKCTRL register using these definitions, both indirectly as well as directly.
GCLK->CLKCTRL.reg = (uint16_t)(GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TCC0_TCC1);
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TCC2_TC3;
The macros GCLK_CLKCTRL_CLKEN etc., are also defined in the component header file.
C99 Designated Initializers
C99 introduced a new feature, designated initializers, which permits you to name the particular member or array element being initialized. For example:
int i;
float f;
int a[2];
};
struct S1 x = {
.f=3.1,
.i=2,
.a[1]=9
};
This feature, when used with register type definitions provided, allows you to create more readable register bitfield initialization code. The following code example initializes GCLK1 with XOSC32K as a source.
GCLK_GENCTRL_Type gclk1_genctrl = {
.bit.RUNSTDBY = 0, /* Generic Clock Generator is stopped in stdby */
.bit.DIVSEL = 0, /* Use GENDIV.DIV value to divide the generator */
.bit.OE = 0, /* Disable generator output to GCLK_IO[1] */
.bit.OOV = 0, /* GCLK_IO[0] output value when generator is off */
.bit.IDC = 1, /* Generator duty cycle is 50/50 */
.bit.GENEN = 1, /* Enable the generator */
.bit.SRC = 0x05, /* Generator source: XOSC32K output */
.bit.ID = 1 /* Generator ID: 1 */
};
// Write these settings
GCLK->GENCTRL.reg = gclk1_genctrl.reg;
CPU Core Register Access (CMSIS)
The CMSIS provides standardized access functions/APIs for accessing the processor's internal peripherals (i.e., NVIC, System Control Block (SCB)) and SystemTick timer (SysTick)) for interrupt control and SysTick initialization. The following example uses CMSIS-Core NVIC APIs to enable the SERCOM3 interrupt and set its priority.
void main(void){
...
// NVIC setup by CMSIS-Core functions
NVIC_SetPriority(SERCOM3_IRQn, 0x0) ; /* set Priority */
NVIC_EnableIRQ(SERCOM3_IRQn) ;
...
}
void SERCOM3_Handler(void){
...
}