Clock Enumerator, SAMD21 Arduino Boards

The SAMD21 has 7 clock sources. Each clock source can drive one or more of the nine Generic Clock Generators. The generic clock generators have a programmable clock divider, and each of the 37 peripheral clocks can be fed from one of the clock generators.

This is a somewhat formidable mess. The Arduino boards that use this microcontroller use four of five clock sources (the Arduino Nano 33 IoT uses one different than the other boards because that board doesn’t have a crystal oscillator). Four clock generators are used, and peripherals are generally assigned a clock generator by the Arduino library when they are first used.

I’ve written this program to enumerate the clock connections. The function report can be called at any time to see what the current assignments are.

/* clock_enumerate -- Clock Enumeration for SAMD21 Arduino boards
  Copyright (C) 2022  Thomas Almy

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

  Contact the author at tom@almy.us */

const char * sources[] = {
  "XOSC",
  "GCLKIN",
  "GCLKGEN1",
  "OSCULP32K",
  "OSC32K",
  "XOSC32K",
  "OSC8M",
  "DFLL48M",
  "FDPLL96M"
};

const char * peripherals[] = {
  "GCM_DFLL48M_REF",
  "GCM_FDPLL96M_INPUT",
  "GCM_FDPLL96M_32K",
  "GCM_WDT",
  "GCM_RTC",
  "GCM_EIC",
  "GCM_USB",
  "GCM_EVSYS_CHANNEL_0",
  "GCM_EVSYS_CHANNEL_1",
  "GCM_EVSYS_CHANNEL_2",
  "GCM_EVSYS_CHANNEL_3",
  "GCM_EVSYS_CHANNEL_4",
  "GCM_EVSYS_CHANNEL_5",
  "GCM_EVSYS_CHANNEL_6",
  "GCM_EVSYS_CHANNEL_7",
  "GCM_EVSYS_CHANNEL_8",
  "GCM_EVSYS_CHANNEL_9",
  "GCM_EVSYS_CHANNEL_10",
  "GCM_EVSYS_CHANNEL_11",
  "GCM_SERCOMx_SLOW",
  "GCM_SERCOM0_CORE",
  "GCM_SERCOM1_CORE",
  "GCM_SERCOM2_CORE",
  "GCM_SERCOM3_CORE",
  "GCM_SERCOM4_CORE",
  "GCM_SERCOM5_CORE",
  "GCM_TCC0_TCC1",
  "GCM_TCC2_TC3",
  "GCM_TC4_TC5",
  "GCM_TC6_TC7",
  "GCM_ADC",
  "GCM_AC_DIG",
  "GCM_AC_ANA",
  "GCM_DAC",
  "GCM_PTC",
  "GCM_I2S_0",
  "GCM_I2S_1"
};

void report(void) {
  int i;
  uint32_t val, val2;

  for  (i = 0; i < 9; i++) {
    *(uint8_t *)&(GCLK->GENCTRL) = i; // Set the ID
    val = GCLK->GENCTRL.reg;
    *(uint8_t *)&(GCLK->GENDIV) = i; // Set the ID
    val2 = GCLK->GENDIV.reg >> 8; // Divisor value
    if (val & GCLK_GENCTRL_GENEN) { // It is enabled
      Serial.print("Generic Clock Generator ");
      Serial.print(i); Serial.print("  Divisor:");
      Serial.print(val2); Serial.print(" Source:");
      Serial.print(sources[(val >> 8) & 0x1f]);
      Serial.print(" GENEN");
      if (val & GCLK_GENCTRL_IDC) Serial.print(" IDC");
      if (val & GCLK_GENCTRL_OOV) Serial.print(" OOV");
      if (val & GCLK_GENCTRL_OE) Serial.print(" OE");
      if (val & GCLK_GENCTRL_DIVSEL) Serial.print(" DIVSEL");
      if (val & GCLK_GENCTRL_RUNSTDBY) Serial.print(" RUNSTDBY");
      Serial.println("");
    }
  }
  Serial.println("");
  for (i = 0; i < 37; i++) {
    *(uint8_t *)&(GCLK->CLKCTRL) = i; // Set the ID
    val = GCLK->CLKCTRL.reg;
    if (val & GCLK_CLKCTRL_CLKEN) {
      Serial.print(peripherals[i]); Serial.print(" from generator ");
      Serial.print((val >> 8) & 0x0f);
      if (val & GCLK_CLKCTRL_WRTLOCK) Serial.print(" Write Lock");
      Serial.println(" Enabled");
    }
  }

}


void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  while (!Serial);
  report();
}

void loop() {
  // put your main code here, to run repeatedly:

}

When run on my Arduino Nano 33 IoT board, the terminal shows:

Generic Clock Generator 0  Divisor:0 Source:DFLL48M GENEN IDC
Generic Clock Generator 1  Divisor:0 Source:OSC32K GENEN
Generic Clock Generator 2  Divisor:0 Source:OSCULP32K GENEN
Generic Clock Generator 3  Divisor:0 Source:OSC8M GENEN

GCM_DFLL48M_REF from generator 1 Enabled
GCM_USB from generator 0 Enabled
GCM_ADC from generator 0 Enabled
GCM_DAC from generator 0 Enabled

Note that GENEN means the clock generator is enabled. Clock generators which aren’t enabled are not listed by the program. The descriptions of the clock sources and the peripherals can be found in the data sheet for the part. DFLL48M is the 48 MHz system clock, which is synthesized from the 32.768 KHz clock source OSC32K [actually not the case, it turns out, see the next blog post]. OSCULP32K is used by the watch dog timer, if enabled, and the Arduino library doesn’t. Finally, OSC8M is an 8 MHz oscillator that is used to boot up the microcontroller, but could be used by peripherals. The peripherals for USB, ADC, and DAC are enabled by the Arduino startup code and use the 48 MHz clock.