Link Search Menu Expand Document

How to read an MCU datasheet (or user guide for the MCU family)

This page is intended to be a very basic overview of how to navigate the datasheet for an MCU, which is typically a document of a thousand (or more!) pages.

Mapping Arduino pins to MCU pins

The MCU on the Arduino MKR1000 is in the SAM D21 family. As can be seen on the MKR1000 schematic, there are multiple components that make up an Arduino board, including the wifi controller and the USB port, and the Arduino board pin headers. These all connect to different pins on the SAM D21. To see which SAM D21 pins the Arduino board pins are connected to, you can look at the right side of the first page of the schematic:

pin_connections

For example, GPIO Pin 1 of your Arduino is connected to pin name PA23 on the SAMD21. “PA” and “PB” stand for “Port A” and “Port B,” respectively.

Datsheet summary

If you want to quickly look up some facts about your MCU, the summary datasheet is the first place to look. The SAM D21 summary is here (59 page document). In this summary, among other things, you can see an overview of the peripherals (such as I/O pins and clocks) the MCU has, look up the memory layout, and understand how the variants in the MCU family differ.

Full datasheet (also known as the Family Datasheet or the Family Users Guide)

While the summary is good for some information at-a-glance, MCUs are far too complicated to describe in a few dozen pages. More information about the MCU can be found in the datasheet. The SAM D21 datasheet is here (warning: 1115 pages). This is a daunting document, so this overview (and the labs in the course) are meant to guide you through navigating it to find the information you need.

A note on navigating the pdf: the file has embedded links (for example, in the table of contents) that will take you to specific chapters. Most PDF readers will also give you a way to navigate to specific chapters of the document using a sidebar (the Chrome pdf viewer, for example, calls it the “document outline”). We recommend having this sidebar open to easily navigate the document.

Registers

A register is a small piece of quickly-accessible memory. You can think of a register as an array of bits of a specific size – common register sizes include 8, 16, 32, and 64 bits. On a CPU, registers are used for many things – keeping track of the program counter, storing temporary data, accessing memory addresses, just to give a few examples. On an MCU, almost every peripheral is controlled by writing to registers – the hardware on the MCU has logic that routes electrical signals around based on the bits that are in specific registers. You can look at the block diagram (either in the summary or the full datasheet) to see how some of these registers are connected to other components on the MCU.

There are some registers that you can write to using software because they are “memory-mapped” – in your code, the register address works just like any other memory address, and the hardware takes care of the routing of those addresses to the appropriate registers. While the Arduino API abstracts these away through pre-compilation and compilation, it is worth understanding this concept to learn how an MCU works. The purpose of using an MCU datasheet is to find out which bits in which registers need to be set or cleared in which order to get your MCU to be configured the way you need.

You can also read from some registers in your programs. The most common use cases are to read a signal from an input pin, and to check on which pin an interrupt occurred.

A note on register mnemonics: for some registers, there are multiple ways to set specific bits. In particular, as you read the datasheet, you may encounter the language of “read-modify-write,” which means that to affect behavior on a certain pin, you first need to read the register, then modify the exact bit(s) you need without changing any of the other bits, and then write the modified value back to the register. For example, if a register reads 0b00010001 and the pin you are trying to affect is controlled by the least significant bit, you would only flip that bit while keeping the others intact, resulting in 0b00010000. This is contrasted with registers that end in SET and CLR, where you can write 0b00000001 to the SET or CLR register to set or clear that particular bit’s functionality without affecting the functionality that corresponds to the other bits. (Other register mnemonics are covered in the table in 45.4).

Reading a peripheral chapter (PORT)

Using the Arduino API, we’ve learned to configure digital pins as input/output and read from/write to them using the functions pinMode, digitalWrite, and digitalRead. As alluded to above, compilation and pre-compilation steps transform these functions into a set of writes to and reads from specific registers on the MCU. Most of these registers are called PORT registers, described in Chapter 23 of the datasheet.

The datasheet chapters that describe peripherals, such as PORT, have the following sections:

  • An overview that describes what the peripherals can do
  • Technical information, including block diagrams and dependencies on other peripherals
  • A functional description, which usually describes the order in which registers must be written to and read from
  • A table of all of the registers for that peripheral
  • Detailed descriptions of the registers, down to the bit level

It is a good idea to skim through the whole chapter on a peripheral when learning about it, just so you know what information is contained within. As a beginner, the sections to pay attention to will be the overview, the dependencies, and the functional description. Prelab 3 will describe which sections to skim and which sections to pay attention to when configuring PORT registers.

Hint: It is also typically much easier to navigate the registers for a specific peripheral using the register summary table, which is typically the second-to-last subsection for a peripheral (e.g. 23.7)!

Clocks

While setting or reading (polling) a GPIO signal is typically not timing-dependent, most other peripherals depend on the on-chip oscillators (clocks) being configured correctly, because everything on an MCU happens in relation to a clock signal. For example, we configure external interrupts (what Arduino’s attachInterrupt does) on the SAM D21 using the EIC (external interrupt controller), described in chapter 21 of the datasheet. If we look at the dependencies for EIC, we see the following text:

"A generic clock (GCLK_EIC) is required to clock the peripheral. This clock must be configured and enabled in the Generic Clock Controller before using the peripheral. Refer to GCLK – Generic Clock Controller."

GCLK is described in chapter 15 of the datasheet. Prelab 3 will guide you through the steps to configure and enable GCLK for interrupts, while Prelab 4 will guide you through more complex clock configuration.

Pin multiplexing

Sometimes, a single pin on an MCU can be configured for different purposes (such as triggering an interrupt or connecting to the internal ADC). Configuring the purpose of such a pin is called multiplexing. For the SAM D21, the table in section 7.1 shows which multiplex functions (A, B, C, D, E, F, G, or H) each PORT pin can take on. You configure the pin’s multiplexer by writing specific bits to its PMUX register to select a specific multiplex function. Again, Prelab 3 will guide you through a specific example of this.

Writing code to configure the registers

To access a specific port register named REGNAME when writing code for the MKR 1000, you can use PORT->Group[PORTX].REGNAME.reg, where PORTX is PORTA or PORTB. If there are multiple numbered registers for the name, you should use the bracket notation, e.g. PORT->Group[PORTA].PMUX[3].reg. To access non-port registers, such as EIC registers, you can simply use EIC->REGNAME.reg.

Instead of manually writing out the bit operations to set certain bits, you can find header files for most MCUs that define macros for the bit names and values. The headers for the SAM D21 that are used by the Arduino IDE are found here. Because the Arduino pre-compilation step pulls in these header files, you can reference the names in these files directly. For example, from gclk.h, we see we can set GENDIV_ID to 6 using

GCLK->GENDIV.reg = 0x0 | GCLK_GENDIV_ID(6); | ...

(note the use of | here, to show that we are potentially setting bits at other positions in the register. The 0x0 is technically redundant here, but is helpful in reminding us that we are resetting the values of the register, instead of keeping some bits intact.)

You can, of course, simply use bit operations as well. In the following example for the read-modify-write PORT register DIR, we use |= to make sure that we are not changing the bits other than the ones we are writing to:

PORT->Group[PORTA].DIR.reg |= (1 << 11)

You can accomplish the same operation using the DIRSET register, where you don’t have to worry about using |=, because SET and CLR registers only cause changes to the bits being written to:

PORT->Group[PORTA].DIRSET.reg = (1 << 11)

Indirect access

Some registers are indirectly addressed (e.g. GENCTRL, GENDIV, and CLKCTRL; see 15.6.4.1). This means that this register controls multiple channel IDs using one register:

indirect

To write to these registers, do not use |=. Be sure to set the bits of the corresponding ID bit group (so that the chip knows which channel you are actually writing to), and set the rest of the bits as desired. The effect is that the ID is being used as an index into the multiple channels – if you write the ID bits, the configuration for that ID will be changed, but not the configurations for the other IDs.

// WRONG:
GCLK->GENCTRL.reg |= GCLK_GENCTRL_ID(6) | ...

// RIGHT:	
GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(6) | ...

To read from these registers, first write only the ID bits, and then read the value of the register, in two separate operations.

GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(6)
// now we can read in GCLK->GENCTRL.reg and get the values for GCLK 6

SYNCBUSY bit

Some registers need to be synchronized when written or read, for example, GENCTRL and GENDIV (see 15.6.6). After writing to such a register, wait for it to synchronize by using

while(GCLK->STATUS.bit.SYNCBUSY);

Note: it is important to heed the indirect access instructions above. If an incorrect ID configuration is written to GENCTRL (for example, when using |=), this will cause the write to GENCTRL to hang, and SYNCBUSY will never clear.