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 APIs, which are also included as part of the standard SAM MCU compiler toolchain in Atmel® Studio.


Summary

For standard MCU peripherals, such as UART, SPI, 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.

atstudio-dependencies.png


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.

If you want or need a refresher on pointers, structures, bit-fields and unions, check out the Fundamentals of C-Programming self-paced learning tutorial.


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:

samd21-port-group0-registers.png

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:

unsigned int *PORT0_DIR_ptr;
PORT0_DIR_ptr = (unsigned int *)(0x41004400);

A value can now be written or read from this memory location.
// read a value from the PORT0_DIR register
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_DIR0 (0x41004400U) /* (PORT) Data Direction Register 0 */
#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 ports.h SAM D21 instance header file:

samd21-ports-instance-header.png
  • 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):

REG_PORT_OUTSET0 = (1 << 30);

Or using the available pin mask macros in the samd21j18a.h pio header file, the same code can be written as:
REG_PORT_OUTSET0 = PORT_PA30;


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:

samd21-device-specific-header-gclk.png

A port IO (pio) header file with the same name (samd21j18a.h, located in a different directory) contains useful port pin macro definitions.

#define PIN_PA30                          30  /**< \brief Pin Number for PA30 */
#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:

samd21-gclk-component-header.png

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 the GCLK CLKCTRL register:

samd21-gclk-clkctrl-component-header.png

There are several ways to access the CLKCTRL register using these definitions, both indirectly as well as directly.

// INDIRECT ACCESS: Enable GCLK for TCC0 (timer counter input clock) on Clock Generator 0
GCLK->CLKCTRL.reg = (uint16_t)(GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TCC0_TCC1);
// DIRECT ACCESS: Enable GCLK for TC3 on Clock Generator 0 and enable the clock to TC3
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.
samd21-component-header-bit-macros.png

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:

struct S1 {
  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 source:
// Configure Generic Clock Generator 1 with XOSC32K as 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;

Omitted field members are implicitly initialized the same as objects that have static storage duration (zero, or zero-like). Make sure you initialize all fields. See: https://gcc.gnu.org/onlinedocs/gcc/Designated-Inits.html


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:

#include "sam.h"

void main(void){
...
// NVIC setup by CMSIS-Core functions
NVIC_SetPriority(SERCOM3_IRQn, 0x0) ;  /* set Priority */
NVIC_EnableIRQ(SERCOM3_IRQn) ;
...
}
void SERCOM3_Handler(void){
...
}

Notes:
  • Interrupt numbers (i.e., SERCOM3_IRQn) are defined in the device-specific header file samd21j18a.h.
  • Peripheral & System interrupt handler names (i.e., SERCOM3_Handler, SysTick_Handler) are also defined in the device-specific header file samd21j18a.h.
© 2024 Microchip Technology, Inc.
Notice: ARM and Cortex are the registered trademarks of ARM Limited in the EU and other countries.
Information contained on this site regarding device applications and the like is provided only for your convenience and may be superseded by updates. It is your responsibility to ensure that your application meets with your specifications. MICROCHIP MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND WHETHER EXPRESS OR IMPLIED, WRITTEN OR ORAL, STATUTORY OR OTHERWISE, RELATED TO THE INFORMATION, INCLUDING BUT NOT LIMITED TO ITS CONDITION, QUALITY, PERFORMANCE, MERCHANTABILITY OR FITNESS FOR PURPOSE. Microchip disclaims all liability arising from this information and its use. Use of Microchip devices in life support and/or safety applications is entirely at the buyer's risk, and the buyer agrees to defend, indemnify and hold harmless Microchip from any and all damages, claims, suits, or expenses resulting from such use. No licenses are conveyed, implicitly or otherwise, under any Microchip intellectual property rights.