How to Write Again to Gclk on Samd21

The Microchip SAM D lineup has a really flexible but sometimes daunting clock system. This post aims to be a gentle introduction to the SAM D21's clock system and a guide for common configurations. It's written as both a guide and a reference - there is a lot of information here and it's definitely not something that anyone is expected to just blot with 1 read. I'k also always open to suggestions and improvements - open an issue on GitHub and let's chat! Also, while information technology'southward written for the SAM D21, this is applicable to most of the chips in the SAM D lineup, including the SAM D11 and SAM D51.

Clock organization overview

The clock organisation for the SAM D21 has a few concepts that are useful to know almost before tackling configuration. There won't be any code examples in this department simply sit tight - I'll go into some code very before long.

Beginning, there are a prepare of clock sources. These are what you generally think of when you think of an "oscillator". Somewhat surprisingly, the clocks sources are configured using the Arrangement Controller (SYSCTRL) peripheral. The SAM D21 has the following clock sources:

  • An eight MHz internal oscillator (OSC8M)
  • A 32.768 kHz high-accurateness internal oscillator (OSC32K)
  • A 32.768 kHz ultra low-ability internal oscillator (OSCULP32K)
  • An internal digital frequency locked loop which can track against other clock sources (DFLL48M)
  • An internal fractional digital phase-locked loop which tin can track against other clock sources (FDPLL96M)

Second, in that location is the Generic Clock Controller (GCLK) peripheral. The general clock peripheral can be seen every bit a "switchboard" of sorts that allows you to connect clock sources to diverse peripherals. For the SAM D21, there are viii generic clocks. Peripherals can exist configured to employ any of these eight clocks and dissimilar peripherals can be clocked from the aforementioned generic clock.

For example, if you wanted to clock the SERCOM0 peripheral from the 8 MHz internal oscillator you would:

  1. Enable the 8 MHz oscillator (OSC8M) using the SYSCTRL peripheral.
  2. Configure a generic clock, for instance, GCLK1 to utilise the OSC8M as its clock source.
  3. Tell the generic clock multiplexer to connect GCLK1 to SERCOM0.

Finally it's of import to know the difference between synchronous and asynchronous clocks. The main arrangement clock which is used to clock the CPU and the internal busses (AHB/APBx) is called synchronous. The generic clocks used to bulldoze peripherals are called asynchronous. The reason for this stardom is because it'south possible (and common) to bulldoze the peripherals using a different clock source than the CPU. Since at that place are potentially two unlike clocks at play, register synchronization must sometimes be done. More on that later - trust me, it'southward much less scary than it sounds.

Configuring the main (CPU) clock

Allow's dive into a concrete example: configuring the main clock and changing the CPU's frequency. The CPU (and the associated busses) are always driven from GCLK0 (you'll sometimes see this referred to as GCLK_MAIN) - so yous can kind of recall of GCLK0 equally special in that way.

Later on reset, the SAM D21 runs at i MHz. The reason for this is that the default state for the clock organisation is:

  • The 8 MHz internal oscillator (OSC8M) is enabled and divided ("prescaled") past 8.
  • Generic clock 0 (GCLK0) is enabled and uses the 8 MHz internal oscillator as its source.

Since the 8 MHz oscillator is divided past 8, GCLK0 is generating a 1 MHz clock for the CPU.

This is equivalent to the hardware running this lawmaking on startup:

          SYSCTRL          ->          OSC8M          .          bit          .          PRESC                              =                              0x03          ;                              // divide by 8          SYSCTRL          ->          OSC8M          .          bit          .          ENABLE                              =                              1          ;                    GCLK          ->          GENCTRL          .          reg                              =                                        GCLK_GENCTRL_ID          (          0          )                              |                                        GCLK_GENCTRL_SRC_OSC8M                              |                                        GCLK_GENCTRL_GENEN          ;                  

A super common thing y'all'll see across SAM D21 firmware examples is that the commencement matter the code does is switch the CPU frequency from 1 MHz to 8 MHz. This is easily washed by irresolute the 8 MHz internal oscillator's prescaler:

          SYSCTRL          ->          OSC8M          .          flake          .          PRESC                              =                              0x0          ;                  

& that'due south information technology! Your SAM D21 is now running as a brisk 8 MHz.

Running the CPU at 48 MHz

Merely hold on - the SAM D21 tin run at a much much faster 48 MHz! How does that work? Well, this requires quite a lot more configuration and involves the internal digital frequency locked loop (DFLL48M).

This DFLL took me a long fourth dimension to wrap my head around. In very basic terms y'all tin think of a frequency locked loop as a clock multiplier. Given a reference clock it can multiply that clock'south frequency by some given corporeality - then if you take a 1 MHz reference clock signal going into a DFLL configured to multiply it by 4, you'll get a 4 MHz clock indicate.

With that in mind you can hopefully run across how the DFLL can generate a 48 MHz clock from a lower frequency reference clock. You have to tell it what clock indicate to follow and tell information technology how much to multiply the rate past.

At that place are 2 separate, but similar ways to configure the DFLL48M to run the CPU at 48 MHz. If y'all want accuracy yous'll use an external crystal oscillator as the reference clock and run in airtight loop fashion. Otherwise, you lot can run in open loop manner.

Using an external crystal and closed-loop fashion

The first arroyo is to use an external 32.768 kHz crystal oscillator as the reference clock. This method requires some external components just allows for the highest accurateness. This is required for a few peripherals - such every bit running the USB peripheral in host mode. This tin await a bit overwhelming but take it one step at a time and y'all'll get it - I hope.

Earlier trying to run at a higher CPU clock, you need to configure the wait states for the internal flash. This makes sure that the CPU doesn't try to access the flash faster than it tin can operate. The right number of wait states for 48 MHz at iii.3v is one. You lot can find the look states for other configurations in Table 37-42 in the datasheet.

          /* Set the correct number of wait states for 48 MHz @ 3.3v */                    NVMCTRL          ->          CTRLB          .          flake          .          RWS                              =                              1          ;                  

With that done, the next footstep is to enable the external crystal oscillator:

          SYSCTRL          ->          XOSC32K          .          reg                              =                                        /* Crystal oscillators can have a long time to startup. This                      waits the maximum amount of time (4 seconds). This can be                      reduced depending on your crystal oscillator. */                                        SYSCTRL_XOSC32K_STARTUP          (          0x7          )                              |                                        SYSCTRL_XOSC32K_EN32K                              |                                        SYSCTRL_XOSC32K_XTALEN          ;                    /* This has to be a separate write as per datasheet section 17.6.3 */                    SYSCTRL          ->          XOSC32K          .          bit          .          ENABLE                              =                              i          ;                    /* Expect for the external crystal to exist ready */                    while          (          !          SYSCTRL          ->          PCLKSR          .          bit          .          XOSC32KRDY          );                  

With the crystal oscillator setup, the adjacent step is to wire it up the a generic clock. This is then information technology can be used equally the reference for the DFLL. In this case it'll be GCLK1, just information technology can exist any clock other than GCLK0. It might seem a little strange to go from a clock source (XOSC32K, 32.768 kHz) to a generic clock (GCLK1) and and then back to clock source (DFLL48M, 48MHz) and finally to some other generic clock (GCLK0, 48Mhz), but that's the way it works!

This code sets up GCLK1 to use XOSC32K as the clock source:

          /* Configure GCLK1's divider - in this case, no partitioning - so just divide by one */                    GCLK          ->          GENDIV          .          reg                              =                                        GCLK_GENDIV_ID          (          1          )                              |                                        GCLK_GENDIV_DIV          (          1          );                    /* Setup GCLK1 using the external 32.768 kHz oscillator */                    GCLK          ->          GENCTRL          .          reg                              =                                        GCLK_GENCTRL_ID          (          1          )                              |                                        GCLK_GENCTRL_SRC_XOSC32K                              |                                        /* Better the duty cycle. */                                        GCLK_GENCTRL_IDC                              |                                        GCLK_GENCTRL_GENEN          ;                    /* Wait for the write to complete */                    while          (          GCLK          ->          Status          .          bit          .          SYNCBUSY          );                  

With GCLK1 now providing the 32.768 kHz reference clock, the next step is to connect GCLK1's output to the DFLL'southward reference clock:

          GCLK          ->          CLKCTRL          .          reg                              =                                        GCLK_CLKCTRL_ID_DFLL48                              |                                        GCLK_CLKCTRL_GEN_GCLK1                              |                                        GCLK_CLKCTRL_CLKEN          ;                  

Now that the reference clock is all configured and connected, you tin at present set upward the DFLL. This is described in particular in section 17.6.7.ane.two of the datasheet. In that location's a lot to wrap your head around here so don't sweat information technology if information technology takes a few reads to get it:

          /* This works around a quirk in the hardware (errata one.2.1) -                      the DFLLCTRL register must be manually reset to this value before                      configuration. */                    while          (          !          SYSCTRL          ->          PCLKSR          .          bit          .          DFLLRDY          );                    SYSCTRL          ->          DFLLCTRL          .          reg                              =                              SYSCTRL_DFLLCTRL_ENABLE          ;                    while          (          !          SYSCTRL          ->          PCLKSR          .          fleck          .          DFLLRDY          );                    /* Set up the multiplier. This tells the DFLL to multiply the 32.768 kHz                      reference clock to 48 MHz */                    SYSCTRL          ->          DFLLMUL          .          reg                              =                                        /* This value is output frequency / reference clock frequency,                      and then 48 MHz / 32.768 kHz */                                        SYSCTRL_DFLLMUL_MUL          (          1465          )                              |                                        /* The fibroid and fine step are used past the DFLL to lock                      on to the target frequency. These are prepare to half                      of the maximum value. Lower values hateful less overshoot,                      whereas higher values typically result in some overshoot merely                      faster locking. */                                        SYSCTRL_DFLLMUL_FSTEP          (          511          )                              |                              // max value: 1023                              SYSCTRL_DFLLMUL_CSTEP          (          31          );                              // max value: 63          /* Wait for the write to finish */                    while          (          !          SYSCTRL          ->          PCLKSR          .          flake          .          DFLLRDY          );                  

Earlier enabling the DFLL, y'all can fix some values to help it initialize faster. When initialized, the DFLL will try to lock on to the the target frequency. It does this by adjusting the DFLLVAL.COARSE and DFLLVAL.FINE registers. There's a factory calibration value for the coarse register that y'all should load that'll get-go the DFLL at a frequency close to 48 MHz and reduce locking time:

          uint32_t                              coarse                              =                              (          *          ((          uint32_t                              *          )          FUSES_DFLL48M_COARSE_CAL_ADDR          )                              &                              FUSES_DFLL48M_COARSE_CAL_Msk          )                              >>                              FUSES_DFLL48M_COARSE_CAL_Pos          ;                    SYSCTRL          ->          DFLLVAL          .          bit          .          COARSE                              =                              coarse          ;                    /* Wait for the write to finish */                    while          (          !          SYSCTRL          ->          PCLKSR          .          chip          .          DFLLRDY          );                  

The last pace in setting upwardly the DFLL is to set it to closed loop style and plough information technology on:

          SYSCTRL          ->          DFLLCTRL          .          reg                              |=                                        /* Closed loop mode */                                        SYSCTRL_DFLLCTRL_MODE                              |                                        /* Wait for the frequency to be locked before outputting the clock */                                        SYSCTRL_DFLLCTRL_WAITLOCK                              |                                        /* Enable information technology */                                        SYSCTRL_DFLLCTRL_ENABLE          ;                    /* Look for the frequency to lock */                    while                              (          !          SYSCTRL          ->          PCLKSR          .          bit          .          DFLLLCKC                              ||                              !          SYSCTRL          ->          PCLKSR          .          bit          .          DFLLLCKF          )                              {}                  

Alright, almost done! The final bit is to switch GCLK0 to apply the DFLL instead of the viii MHz internal oscillator. As soon every bit this is washed the CPU will exist running at 48 MHz:

          /* Setup GCLK0 using the DFLL @ 48 MHz */                    GCLK          ->          GENCTRL          .          reg                              =                                        GCLK_GENCTRL_ID          (          0          )                              |                                        GCLK_GENCTRL_SRC_DFLL48M                              |                                        /* Improve the duty cycle. */                                        GCLK_GENCTRL_IDC                              |                                        GCLK_GENCTRL_GENEN          ;                    /* Wait for the write to complete */                    while          (          GCLK          ->          Status          .          bit          .          SYNCBUSY          );                  

That'due south it! Take a deep breath, that was a lot! To review:

  1. Gear up XOSC32K in SYSCTRL.
  2. Configure GCLK1 to utilise XOSC32K as its clock source.
  3. Connect GCLK1 to the DFLL reference clock input.
  4. Configure DFLL48M to multiply the 32.768 kHz reference to 48 MHz.
  5. Configure GCLK0 to use the DFLL48M as its clock source.

Using open loop mode

If you don't need the accuracy that comes from using an external 32.768 kHz crystal, you can fix up the DFLL in open up loop fashion. This means that the DFLL isn't post-obit whatever specific reference clock, it's just outputting a frequency directly based on its configuration.

As with closed loop mode, y'all'll need to set upwardly the proper flash wait states:

          /* Set the correct number of wait states for 48 MHz @ 3.3v */                    NVMCTRL          ->          CTRLB          .          bit          .          RWS                              =                              ane          ;                  

With that taken care of, the next step is to load the coarse and fine values for the DFLL from the manufacturing plant calibration. This will get the DFLL pretty shut to 48 MHz:

          /* This works around a quirk in the hardware (errata ane.ii.1) -                      the DFLLCTRL register must be manually reset to this value before                      configuration. */                    while          (          !          SYSCTRL          ->          PCLKSR          .          scrap          .          DFLLRDY          );                    SYSCTRL          ->          DFLLCTRL          .          reg                              =                              SYSCTRL_DFLLCTRL_ENABLE          ;                    while          (          !          SYSCTRL          ->          PCLKSR          .          scrap          .          DFLLRDY          );                    /* Write the fibroid and fine calibration from NVM. */                    uint32_t                              coarse                              =                                        ((          *          (          uint32_t          *          )          FUSES_DFLL48M_COARSE_CAL_ADDR          )                              &                              FUSES_DFLL48M_COARSE_CAL_Msk          )                              >>                              FUSES_DFLL48M_COARSE_CAL_Pos          ;                    uint32_t                              fine                              =                                        ((          *          (          uint32_t          *          )          FUSES_DFLL48M_FINE_CAL_ADDR          )                              &                              FUSES_DFLL48M_FINE_CAL_Msk          )                              >>                              FUSES_DFLL48M_FINE_CAL_Pos          ;                    SYSCTRL          ->          DFLLVAL          .          reg                              =                              SYSCTRL_DFLLVAL_COARSE          (          coarse          )                              |                              SYSCTRL_DFLLVAL_FINE          (          fine          );                    /* Wait for the write to terminate. */                    while                              (          !          SYSCTRL          ->          PCLKSR          .          chip          .          DFLLRDY          )                              {};                  

Now you lot can go ahead and enable the DFLL:

          /* Enable the DFLL */                    SYSCTRL          ->          DFLLCTRL          .          flake          .          ENABLE                              =                              ane          ;                    /* Wait for the write to finish */                    while                              (          !          SYSCTRL          ->          PCLKSR          .          bit          .          DFLLRDY          )                              {};                  

But as with the closed loop example, the terminal footstep is to switch GCLK0 to employ the DFLL every bit its clock source:

          /* Setup GCLK0 using the DFLL @ 48 MHz */                    GCLK          ->          GENCTRL          .          reg                              =                                        GCLK_GENCTRL_ID          (          0          )                              |                                        GCLK_GENCTRL_SRC_DFLL48M                              |                                        /* Improve the duty wheel. */                                        GCLK_GENCTRL_IDC                              |                                        GCLK_GENCTRL_GENEN          ;                    /* Wait for the write to complete */                    while          (          GCLK          ->          Status          .          scrap          .          SYNCBUSY          );                  

USB clock recovery manner

I know you lot're thinking "wait- you said at that place were two ways to configure 48 MHz". Well, that's still true. USB clock recovery mode is a small change to the open loop strategy. USB clock recovery mode (detailed in section 17.half dozen.7.2.2 of the datasheet) is a neat way of generating a 48 MHz clock from the USB connection. If the SAM D21 is used as a USB device then the DFLL can be configured to use the ane kHz USB start-of-frame every bit the reference clock. This is actually required if you lot don't have an external crystal and y'all desire to operate every bit a USB device. It's a bully feature that allows for some price saving when designing hardware.

The way to set this up is to take the open loop configuration equally a starting indicate:

          /* Set the right number of look states for 48 MHz @ 3.3v */                    NVMCTRL          ->          CTRLB          .          bit          .          RWS                              =                              1          ;                    /* This works around a quirk in the hardware (errata ane.2.1) -                      the DFLLCTRL register must exist manually reset to this value before                      configuration. */                    while          (          !          SYSCTRL          ->          PCLKSR          .          bit          .          DFLLRDY          );                    SYSCTRL          ->          DFLLCTRL          .          reg                              =                              SYSCTRL_DFLLCTRL_ENABLE          ;                    while          (          !          SYSCTRL          ->          PCLKSR          .          bit          .          DFLLRDY          );                    /* Write the coarse and fine calibration from NVM. */                    uint32_t                              fibroid                              =                                        ((          *          (          uint32_t          *          )          FUSES_DFLL48M_COARSE_CAL_ADDR          )                              &                              FUSES_DFLL48M_COARSE_CAL_Msk          )                              >>                              FUSES_DFLL48M_COARSE_CAL_Pos          ;                    uint32_t                              fine                              =                                        ((          *          (          uint32_t          *          )          FUSES_DFLL48M_FINE_CAL_ADDR          )                              &                              FUSES_DFLL48M_FINE_CAL_Msk          )                              >>                              FUSES_DFLL48M_FINE_CAL_Pos          ;                    SYSCTRL          ->          DFLLVAL          .          reg                              =                              SYSCTRL_DFLLVAL_COARSE          (          coarse          )                              |                              SYSCTRL_DFLLVAL_FINE          (          fine          );                    /* Await for the write to stop. */                    while                              (          !          SYSCTRL          ->          PCLKSR          .          bit          .          DFLLRDY          )                              {};                  

Side by side, configure the settings to enable USB clock recovery style:

          SYSCTRL          ->          DFLLCTRL          .          reg                              |=                                        /* Enable USB clock recovery mode */                                        SYSCTRL_DFLLCTRL_USBCRM                              |                                        /* Disable chill bicycle every bit per datasheet to speed up locking.                      This is specified in section 17.6.seven.2.2, and chill cycles                      are described in section 17.6.7.ii.1. */                                        SYSCTRL_DFLLCTRL_CCDIS          ;                    /* Configure the DFLL to multiply the 1 kHz clock to 48 MHz */                    SYSCTRL          ->          DFLLMUL          .          reg                              =                                        /* This value is output frequency / reference clock frequency,                      so 48 MHz / i kHz */                                        SYSCTRL_DFLLMUL_MUL          (          48000          )                              |                                        /* The coarse and fine values can be gear up to their minimum                      since coarse is fixed in USB clock recovery mode and                      fine should lock on quickly. */                                        SYSCTRL_DFLLMUL_FSTEP          (          i          )                              |                                        SYSCTRL_DFLLMUL_CSTEP          (          1          );                  

Then set the DFLL into airtight loop way and enable it:

          /* Closed loop mode */                    SYSCTRL          ->          DFLLCTRL          .          bit          .          MODE                              =                              ane          ;                    /* Enable the DFLL */                    SYSCTRL          ->          DFLLCTRL          .          bit          .          ENABLE                              =                              1          ;                    /* Wait for the write to complete */                    while                              (          !          SYSCTRL          ->          PCLKSR          .          scrap          .          DFLLRDY          )                              {};                  

This tin be a little disruptive but here's what happens:

  • If at that place isn't a USB connectedness then things piece of work just as they did in open up loop mode. The DFLL outputs a clock close to 48 MHz and does not have a reference clock.
  • When there is a USB connectedness and then things work similar to the manner they did in airtight loop mode. The DFLL follows the ane kHz USB start of frame message and outputs a much more than accurate 48 MHz clock.

After setting all of this up the concluding step is again to tell GCLK0 to use the DFLL as its clock source:

          /* Setup GCLK0 using the DFLL @ 48 MHz */                    GCLK          ->          GENCTRL          .          reg                              =                                        GCLK_GENCTRL_ID          (          0          )                              |                                        GCLK_GENCTRL_SRC_DFLL48M                              |                                        /* Improve the duty cycle. */                                        GCLK_GENCTRL_IDC                              |                                        GCLK_GENCTRL_GENEN          ;                    /* Wait for the write to complete */                    while          (          GCLK          ->          Condition          .          bit          .          SYNCBUSY          )                              {};                  

Peripheral clocks

Alright, at present that you're a main of the synchronous chief clock let's talk about peripheral clocks. Each of the SAM D21's peripherals bargain with two unlike clock domains:

  1. The peripheral interface clock: this is the synchronous clock from the CPU & associated busses (AHB/APBx).
  2. The peripheral cadre clock: this is the asynchronous clock that'southward configured and wired up to the peripheral using the GCLK peripheral.

The first one y'all've already prepare up - it's the CPU clock. Information technology comes into play in a bit in the register synchronization section. The 2nd - the peripheral core clock is what this section covers.

To enable a peripheral that needs a clock, you'll need to:

  1. Ready a generic clock generator (GCLK) using one of the clock sources. Retrieve that multiple peripherals can share the same GCLK, and you tin use GCLK0 to clock peripherals as well.
  2. Configure the generic clock multiplexer to connect the GCLK to the peripheral.
  3. Enable the peripheral'southward interface clock using the power direction (PM) peripheral.

Here'due south a physical example: let'south configure the clock for SERCOM0.

The first pace is to setup a generic clock generator. You could use GCLK0, just I usually apply a generic clock that uses the 8 MHz oscillator as a clock source for SERCOM peripherals. And then this configures GCLK2 to use the 8 MHz oscillator:

          /* Configure GCLK2'due south divider - in this case, no division - and then just divide past i */                    GCLK          ->          GENDIV          .          reg                              =                                        GCLK_GENDIV_ID          (          2          )                              |                                        GCLK_GENDIV_DIV          (          1          );                    /* Setup GCLK2 using the internal 8 MHz oscillator */                    GCLK          ->          GENCTRL          .          reg                              =                                        GCLK_GENCTRL_ID          (          2          )                              |                                        GCLK_GENCTRL_SRC_OSC8M                              |                                        /* Improve the duty cycle. */                                        GCLK_GENCTRL_IDC                              |                                        GCLK_GENCTRL_GENEN          ;                    /* Wait for the write to complete */                    while          (          GCLK          ->          Condition          .          flake          .          SYNCBUSY          )                              {};                  

The second step is to tell the clock multiplexer to connect GCLK2 to SERCOM0:

          /* Connect GCLK2 to SERCOM0 */                    GCLK          ->          CLKCTRL          .          reg                              =                                        GCLK_CLKCTRL_CLKEN                              |                                        GCLK_CLKCTRL_GEN_GCLK2                              |                                        GCLK_CLKCTRL_ID_SERCOM0_CORE          ;                    /* Await for the write to complete. */                    while                              (          GCLK          ->          STATUS          .          bit          .          SYNCBUSY          )                              {};                  

Finally enable the peripheral interface clock using the power management peripheral:

          PM          ->          APBCMASK          .          reg                              |=                              PM_APBCMASK_SERCOM0          ;                  

At this bespeak the peripheral is prepare to use! You can software reset the peripheral and showtime configuring it equally needed:

          SERCOM0          ->          SPI          .          CTRLA          .          flake          .          SWRST                              =                              ane          ;                    while                              (          SERCOM0          ->          SPI          .          SYNCBUSY          .          bit          .          SWRST          )                              {};                    ...                  

Register synchronization

Yous might have noticed that there are a lot of references to SYNCBUSY above. This is a event of register synchronization. This is needed because there's two clocks at play when interacting with a peripheral - one clock for the CPU and the busses and a generic clock that'southward running the peripheral cadre. A direct example is that the configuration above runs the CPU at 48 MHz and SERCOM0 at 8 MHz. Since the cadre is accessing the same registers as the CPU, some writes and reads need to be synchronized.

When reading the datasheet's register descriptions for a peripheral you'll see the term "Write-Synchronized". This ways that when you write to this annals the hardware volition prepare the peripheral's SYNCBUSY register while synchronizing the write. For example:

          /* Enable SERCOM0 */                    SERCOM0          ->          SPI          .          CTRLA          .          flake          .          ENABLE                              =                              1          ;                    /* Await for the write to complete before modifying any other SERCOM0 registers */                    while                              (          SERCOM0          ->          SPI          .          SYNCBUSY          .          bit          .          ENABLE          )                              {};                  

If you try to write to a peripheral's register while it's decorated synchronizing, the CPU will stall until the synchronization is consummate.

You can read more about register synchronization in section fourteen.3 of the datasheet.

Debugging clocks with an oscilloscope

If you take access to an oscilloscope you can accept advantage of a nice feature of the SAM D chips: outputting a generic clock (GCLK) to an I/O pivot. This is super useful when you're trying to verify that all of your clocks are working correctly. When debugging this way I highly recommend that you don't mess with GCLK0 at all so that you don't have to worry well-nigh a bad configuration breaking the CPU clock.

To enable clock output you demand to set the OE bit when configuring a generic clock through GCLK->GENCTRL. For example, this configures GCLK1 and enables I/O output:

          GCLK          ->          GENCTRL          .          reg                              =                                        GCLK_GENCTRL_ID          (          i          )                              |                                        GCLK_GENCTRL_SRC_XOSC32K                              |                                        GCLK_GENCTRL_IDC                              |                                        GCLK_GENCTRL_GENEN                              |                                        /* Enable outputting the clock to an I/O pin. */                                        GCLK_GENCTRL_OE          ;                    /* Wait for the write to complete */                    while          (          GCLK          ->          STATUS          .          bit          .          SYNCBUSY          );                  

In one case that's configured you'll need to configure the port multiplexer to connect the GCLK output to a pin. For example, this sets up PA15 to output GCLK1:

          PORT          ->          Grouping          [          0          ].          DIRSET          .          reg                              =                              (          1                              <<                              fifteen          );                    PORT          ->          Group          [          0          ].          PINCFG          [          xv          ].          reg                              |=                              PORT_PINCFG_PMUXEN          ;                    PORT          ->          Group          [          0          ].          PMUX          [          15                              >>                              1          ].          bit          .          PMUXO                              |=                              PORT_PMUX_PMUXO_H          ;                  

With that complete you lot can connect an oscilloscope to PA15 and find the clock:

A 32kHz clock from GCLK1

Like many other I/O peripherals there's only a sure fix of pins you can employ for this. This is covered in item in the port multiplexing section of the datasheet, but here's a handy table for the SAM D21:

GCLK I/O Pins Mux
GCLK0 PA14, PB14, PB22, PA27, PA28, PA30 H
GCLK1 PA15, PB15, PB23 H
GCLK2 PA16, PB16 H
GCLK3 PA17, PB17 H
GCLK4 PA10, PB10, PA20 H
GCLK5 PA11, PB11, PA21 H
GCLK6 PA12, PA22 H
GCLK7 PB13, PA23 H

Some tips and miscellanea

  • Failing to unmask the the peripheral in the power management (PM) peripheral and trying to access any peripheral registers will lock the CPU.
  • Synchronization stalls aren't usually that large of a deal, but explicitly handling them makes sure that interrupts volition be properly serviced while waiting for synchronization.
  • It might seem odd that there are three dissimilar options for a 32.768 kHz oscillator. This mystery is solved by consulting the electric characteristics section of the datasheet (section 37.13). Using an external crystal (XOSC32K) volition give a very accurate frequency of 32.768 kHz with about 100ppm of drift, but it'll consume around two.19 μA. OSC32K has a frequency betwixt 32.276 - 33.260 kHz and consumes at near 1.31 μA. Finally, OSCULP32K has a frequency between 31.293 - 34.570 kHz and consumes at most 125 nA. Information technology basically comes down to a tradeoff between accurateness and power consumption. In many cases you can use the ultra low ability internal oscillator (OSCULP32K) and save a lot on power consumption.
  • The DFLL48M in open loop way volition output a frequency between 47 MHz - 49 MHz every bit long as you load the factory fibroid calibration value. In closed loop mode with an external crystal oscillator it'll be between 47.963 MHz - 47.981 MHz.
  • Pay shut attending to the maximum clock that a peripheral can run at. For instance, the DAC peripheral can only run at 350 kHz (datasheet table 37-6). Besides annotation that some peripherals can accept a loftier frequency clock input but they must configure their peripheral clock divider to be lower - for case, the ADC peripheral tin can take an input clock of 48MHz only can but run at ii.one MHz (datasheet table 37-24), so you would have to configure ADC->CTRLB.bit.PRESCALER appropriately.

References and farther reading

  • Microchip Programmer - SAM D21 Clock Configuration
  • CircuitPython's clock initialization
  • Datasheet
  • Errata

weavercoperfell.blogspot.com

Source: https://blog.thea.codes/understanding-the-sam-d21-clocks/

0 Response to "How to Write Again to Gclk on Samd21"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel