Startup Without Peripheral Libraries – ATSAME54P20A

Startup without peripheral libraries series

In order to configure the ATSAME54P20A, we have to talk about the clock system.  The SAME5x/SAMD5x family has one of the more complicated clocking systems I have seen in a microcontroller (the following figure is taken from the SAM-D5XE5X Family Datasheet, page 144):

There are five clock sources:

  • XOSC0 – for connecting an external oscillator
  • XOSC1 – for connection an external oscillator
  • DFLL – 48MHz digital frequency locked loop that can take an external 32kHz input for closed loop control or run without a reference in open loop control
  • XOSC32K – for connecting an external 32.768kHz crystal
  • OSCULP32K – ultra low power internal 32.768kHz oscillator

Each clock source can connect into any of the 12 Generic Clock Generators, that can divide the source clock, or directly into either of the two digital phase lock loops: DPLL0 and DPLL1.  The DPLL’s multiply the clock source into any multitude of frequencies.  The DPLL’s can also take a Generic Clock Generator as a reference.  Plus, the Generic Clock Generators can take Generator 1 as a source and further divide it.  Generator 0 is special and is always used as the CPU clock.

Once a clock source is connected to a generator, the generator is used to provide a clock signal to the peripherals.  This complexity makes the clock system unbelievably flexible.  All peripherals can run at different clock rates depending on need.

Now that we have discussed the clock system, the steps to getting the MCU running are similar to the STM32F446.  Atmel Studio includes the startup code that sets up generic interrupts and jumps to the main loop.  All that is required to access the device register headers is including “sam.h”.

The example I give configures the MCU to run with a 12MHz crystal on XOSC1.  This is input into DPLL0 and DPLL1 and output at 120MHz and 200MHz respectively (TCC and PDEC can run at 200MHz).  DPLL0 is connected to generic clock 0, DPLL1 is connected to generic clock 1 and generic clock 2.  Generic Clock 2 divides DPLL1 by two to obtain a 100MHz clock (used by EVSYS, SERCOM, etc).

The startup code is as follows:

  1. Set up the clocks!
  2. Enable any peripheral clocks that are needed.  I include ADC0 and EVSYS as an example.
  3. Enable the cache.  The wait states are calculated automatically for the clock.
  4. Configure any peripherals.  In this case, I set up SysTick at 1ms.  The interrupt callback function is defined in the startup code and is appropriately named SysTick_Handler.
  5. The sysInit function is called before the main loop
#include "sam.h"

void sysInit(void)
{
   //
   // Enable clocks
   //
   // Run with a 12MHz external crystal on XOSC1
   OSCCTRL->XOSCCTRL[1].bit.ENALC = 1;
   OSCCTRL->XOSCCTRL[1].bit.IMULT = 4;
   OSCCTRL->XOSCCTRL[1].bit.IPTAT = 3;
   OSCCTRL->XOSCCTRL[1].bit.ONDEMAND = 0;
   OSCCTRL->XOSCCTRL[1].bit.XTALEN = 1;
   OSCCTRL->XOSCCTRL[1].bit.ENABLE = 1;
   // Wait for OSC to be ready
   while (0 == OSCCTRL->STATUS.bit.XOSCRDY1);

   // Set up DPLL0 to output 120MHz using XOSC1 output divided by 12 - max input to the PLL is 3.2MHz
   OSCCTRL->Dpll[0].DPLLRATIO.bit.LDRFRAC = 0;
   OSCCTRL->Dpll[0].DPLLRATIO.bit.LDR = 119;
   OSCCTRL->Dpll[0].DPLLCTRLB.bit.DIV = 5; // 2 * (DIV + 1)
   OSCCTRL->Dpll[0].DPLLCTRLB.bit.REFCLK = 3; // use XOSC1 clock reference
   OSCCTRL->Dpll[0].DPLLCTRLA.bit.ONDEMAND = 0;
   OSCCTRL->Dpll[0].DPLLCTRLA.bit.ENABLE = 1; // enable the PLL
   // Wait for PLL to be locked and ready
   while(0 == OSCCTRL->Dpll[0].DPLLSTATUS.bit.LOCK || 0 == OSCCTRL->Dpll[0].DPLLSTATUS.bit.CLKRDY);

   // Set up DPLL1 to output 200MHz using XOSC1 output divided by 12 - max input to the PLL is 3.2MHz
   OSCCTRL->Dpll[1].DPLLRATIO.bit.LDRFRAC = 0;
   OSCCTRL->Dpll[1].DPLLRATIO.bit.LDR = 199;
   OSCCTRL->Dpll[1].DPLLCTRLB.bit.DIV = 5; // 2 * (DIV + 1)
   OSCCTRL->Dpll[1].DPLLCTRLB.bit.REFCLK = 3; // use XOSC1 clock reference
   OSCCTRL->Dpll[1].DPLLCTRLA.bit.ONDEMAND = 0;
   OSCCTRL->Dpll[1].DPLLCTRLA.bit.ENABLE = 1; // enable the PLL
   // Wait for PLL to be locked and ready
   while(0 == OSCCTRL->Dpll[1].DPLLSTATUS.bit.LOCK || 0 == OSCCTRL->Dpll[1].DPLLSTATUS.bit.CLKRDY);

   // Each GCLK has to be enabled and the divider set in a single 32-bit write
   // Connect DPLL0 to clock generator 0 (120MHz) - frequency used by CPU, AHB, APBA, APBB
   GCLK->GENCTRL[0].reg = GCLK_GENCTRL_SRC_DPLL0 | GCLK_GENCTRL_DIV(1) | GCLK_GENCTRL_GENEN;
   while (1 == GCLK->SYNCBUSY.bit.GENCTRL0);

   // Connect DPLL1 to clock generator 1 (200MHz) - frequency used by TCC, PDEC
   GCLK->GENCTRL[1].reg = GCLK_GENCTRL_SRC_DPLL1 | GCLK_GENCTRL_DIV(1) | GCLK_GENCTRL_GENEN;
   while (1 == GCLK->SYNCBUSY.bit.GENCTRL1);

   // DPLL1 to clock generator 2 and divide by 2 (100MHz) - frequency used by EVSYS, SERCOM, CAN, ADC, DAC
   GCLK->GENCTRL[2].reg = GCLK_GENCTRL_SRC_DPLL1 | GCLK_GENCTRL_DIV(2) | GCLK_GENCTRL_GENEN;
   while (1 == GCLK->SYNCBUSY.bit.GENCTRL2);

   //
   // Enable peripheral clocks
   //
   // EVSYS0
   MCLK->APBBMASK.bit.EVSYS_ = 1;
   GCLK->PCHCTRL[11].reg = GCLK_PCHCTRL_GEN_GCLK2 | GCLK_PCHCTRL_CHEN;
   while (0 == GCLK->PCHCTRL[11].bit.CHEN);
   // ADC0 - gen 3
   MCLK->APBDMASK.bit.ADC0_ = 1;
   GCLK->PCHCTRL[40].reg = GCLK_PCHCTRL_GEN_GCLK2 | GCLK_PCHCTRL_CHEN;
   while (0 == GCLK->PCHCTRL[40].bit.CHEN);
   // SERCOM1 - gen 3
   MCLK->APBAMASK.bit.SERCOM1_ = 1;
   GCLK->PCHCTRL[8].reg = GCLK_PCHCTRL_GEN_GCLK2 | GCLK_PCHCTRL_CHEN;
   while (0 == GCLK->PCHCTRL[8].bit.CHEN);

   //
   // Set up Cache
   //
   CMCC->CTRL.bit.CEN = 1; // enable the cache

   //
   // Set up SysTick - 1ms
   //
   SysTick_Config(120000);
}