Seeeduino Xiao - CPU Clock - Slow Operations

Hey all. I recently switch over from the Pro Micro to the Seeediuno Xiao. Is a good board, love it so far.

After much time, I found out how to get the clock speed to the 48MHz speed and able to output it to a Pin and verify on my oscilloscope.

I am running FFT for a spectrum analyzer and have my test code just throwing random numbers in the array. What is puzzling is, when I count the millis() before and after I enable the clock settings, the time it takes to compute the FFT still stays the same... 109 millis for 512 samples. Am I possibly enabling the clocks wrong or not enabling the right thing or my logic is just silly?

Am using the develop version of arduinofft... meaning I can use floats instead of doubles for faster math operations.

I figured up the clock, to perform more operations per second, but im still at 100 milliseconds either way.

Any advice would be appreciated. Even if you just have a SAMD21 mc.

// I/O Ports definitions
#define PORTA     (0ul)
#define PORTB     (1ul)

//peripheral to link to output pin (DFLL48)
#define PERIPHERAL_H  0x07

// Constants for Clock Generators
#define GENERIC_CLOCK_GENERATOR_0   (0u)
#define GENERIC_CLOCK_GENERATOR_1   (1u)
#define GENERIC_CLOCK_GENERATOR_2   (2u)
#define GENERIC_CLOCK_GENERATOR_3   (3u)
#define GENERIC_CLOCK_GENERATOR_4   (4u)

//Constants for clock identifiers
#define CLOCK_XOSC32K  0x05
#define CLOCK_DFLL48  0x07
#define CLOCK_8MHZ    0x06
//////////// Normally a header file, copied for internets

#include <arduinoFFT.h>

#define SAMPLES    512
#define SAMPLING_FREQ 30000

unsigned int sampling_period_us;
unsigned long newTime;
float vReal[SAMPLES];
float vImag[SAMPLES];
int fftBin[8];
int fftCalc[8];
float weighingFactors[SAMPLES];

//arduinoFFT FFT = arduinoFFT(vReal, vImag, SAMPLES, SAMPLING_FREQ);
ArduinoFFT<float> FFT = ArduinoFFT<float>(vReal, vImag, SAMPLES, SAMPLING_FREQ, weighingFactors);

void setup() {
  // put your setup code here, to run once:
  gclkSetup();
  
  //sampling_period_us = round(1000000 * (1.0 / SAMPLING_FREQ));
  SerialUSB.begin(9600);
  while (!SerialUSB) { }
}

void loop() {
  long t0, t;

  t0 = millis();
  for(int i=0; i<SAMPLES; i++) {
    vReal[i] = i*SAMPLES;
    vImag[i] = 0;
    //while ((micros() - newTime) < sampling_period_us) { /* chill */ }
  }

    //-- Compute FFT
  //FFT.DCRemoval();
  //FFT.windowing(FFTWindow::Hann,FFTDirection::Forward);
  //FFT.Windowing(FFTWindow::Hamming, FFTDirection::Forward);
  FFT.windowing(FFTWindow::Flat_top, FFTDirection::Forward);         // Flat Top Window - better amplitude accuracy
  FFT.compute(FFTDirection::Forward);
  FFT.complexToMagnitude();

  addFFT();
  
  t = millis()-t0;  // calculate elapsed time

  SerialUSB.print("Time: ");
  SerialUSB.println((int)t);
}

void addFFT() {

    // Reset fftBin & fftCalc
  for (int i = 0; i<8; i++){
    fftBin[i] = 0;
    fftCalc[i] = 0;
  }

  // Analyse FFT results based on old FHT library.
  for (int i = 0; i < (SAMPLES/2); i++){
    //8 bins
    if (i<=0 )           fftCalc[0]  += vReal[i];
    if (i>0   && i<=1  ) fftCalc[1]  += vReal[i];
    if (i>1   && i<=2  ) fftCalc[2]  += vReal[i];
    if (i>2   && i<=6  ) fftCalc[3]  += vReal[i];
    if (i>6   && i<=13 ) fftCalc[4]  += vReal[i];
    if (i>13  && i<=31 ) fftCalc[5]  += vReal[i];
    if (i>31  && i<=73 ) fftCalc[6]  += vReal[i];
    if (i>73           ) fftCalc[7]  += vReal[i];
  }
}

void gclkSetup() {

  //NVM CTRLB registers and RWS[3:0] bit for setting wait states fro a read operation  //Defaults to 0 and can go as high as 15 (4 bits)
  NVMCTRL->CTRLB.bit.RWS        = 1;    // 1 wait state required @ 3.3V & 48MHz

  //the system controller subsystem controls the clocks. The XOSC32K register sets up the External 32.768kHz oscillator
  SYSCTRL->XOSC32K.bit.WRTLOCK  = 0;    // XOSC32K configuration is not locked
  SYSCTRL->XOSC32K.bit.STARTUP  = 0x2;  // 3 cycle start-up time
  SYSCTRL->XOSC32K.bit.ONDEMAND = 0;    // Osc. is always running when enabled
  SYSCTRL->XOSC32K.bit.RUNSTDBY = 0;    // Osc. is disabled in standby sleep mode
  SYSCTRL->XOSC32K.bit.AAMPEN   = 0;    // Disable automatic amplitude control
  SYSCTRL->XOSC32K.bit.EN32K    = 1;    // 32kHz output is enable
  SYSCTRL->XOSC32K.bit.XTALEN   = 1;    // Crystal connected to XIN32/XOUT32
  // Enable the Oscillator - Separate step per data sheet recommendation (sec 17.6.3)
  SYSCTRL->XOSC32K.bit.ENABLE   = 1;
  // Wait for XOSC32K to stabilize
  while (!SYSCTRL->PCLKSR.bit.XOSC32KRDY);

  //Generic clock subsystem setting GENDIV register to set the divide factor for Generic clock 1
  GCLK->GENDIV.reg |= GCLK_GENDIV_DIV(1) | GCLK_GENDIV_ID(GENERIC_CLOCK_GENERATOR_1); //set divide factor for gen clock 1

  // Configure Generic Clock Generator 1 with XOSC32K as source
  GCLK->GENCTRL.bit.RUNSTDBY = 0;                         // Generic Clock Generator is stopped in stdby
  GCLK->GENCTRL.bit.DIVSEL   = 0;                         // enable clock divide
  GCLK->GENCTRL.bit.OE       = 0;                         // Disable generator output to GCLK_IO[1]
  GCLK->GENCTRL.bit.OOV      = 0;                         // We will not use this signal as an output
  GCLK->GENCTRL.bit.IDC      = 1;                         // Generator duty cycle is 50/50
  GCLK->GENCTRL.bit.GENEN    = 1;                         // Enable the generator
  GCLK->GENCTRL.bit.SRC      = CLOCK_XOSC32K;             // Generator source: XOSC32K output
  GCLK->GENCTRL.bit.ID       = GENERIC_CLOCK_GENERATOR_1; // This was created in Definitions.h, refers to generic clock 1
  // GENCTRL is Write-Synchronized...so wait for write to complete
  while (GCLK->STATUS.bit.SYNCBUSY);

  //Enable the Generic Clock  // Generic Clock Generator 1 is the source  //Generic Clock Multiplexer 0 (DFLL48M Reference)
  GCLK->CLKCTRL.reg |= GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN(GENERIC_CLOCK_GENERATOR_1) | GCLK_CLKCTRL_ID_DFLL48;

  // DFLL Configuration in Closed Loop mode, cf product data sheet chapter
  // 17.6.7.1 - Closed-Loop Operation
  // Enable the DFLL48M in open loop mode. Without this step, attempts to go into closed loop mode at 48 MHz will
  // result in Processor Reset (you'll be at the in the Reset_Handler in startup_samd21.c).
  // PCLKSR.DFLLRDY must be one before writing to the DFLL Control register
  // Note that the DFLLRDY bit represents status of register synchronization - NOT clock stability
  // (see Data Sheet 17.6.14 Synchronization for detail)
  while (!SYSCTRL->PCLKSR.bit.DFLLRDY);
  SYSCTRL->DFLLCTRL.reg = (uint16_t)(SYSCTRL_DFLLCTRL_ENABLE);
  while (!SYSCTRL->PCLKSR.bit.DFLLRDY);

  // Set up the Multiplier, Coarse and Fine steps. These values help the clock lock at the set frequency
  //There is not much information in the datasheet on what they do exactly and how to tune them for your specific needs
  //lower values lead to more "overshoot" but faster frequency lock. Higher values lead to less "overshoot" but slower lock time
  //Datasheet says put them at half to get best of both worlds
  SYSCTRL->DFLLMUL.bit.CSTEP = 31; //max value is 2^6 - 1 or 63
  SYSCTRL->DFLLMUL.bit.FSTEP = 511; //max value is 2^10 - 1 or 1023
  SYSCTRL->DFLLMUL.bit.MUL = 1465; //multiplier of ref external clock to get to 48M --> 32768 x 1465 = 48,005,120
  // Wait for synchronization
  while (!SYSCTRL->PCLKSR.bit.DFLLRDY);
  // To reduce lock time, load factory calibrated values into DFLLVAL (cf. Data Sheet 17.6.7.1)
  // Location of value is defined in Data Sheet Table 10-5. NVM Software Calibration Area Mapping

  uint32_t tempDFLL48CalibrationCoarse; // used to retrieve DFLL48 coarse calibration value from NVM
  // Get factory calibrated value for "DFLL48M COARSE CAL" from NVM Software Calibration Area
  tempDFLL48CalibrationCoarse = *(uint32_t*)FUSES_DFLL48M_COARSE_CAL_ADDR;
  tempDFLL48CalibrationCoarse &= FUSES_DFLL48M_COARSE_CAL_Msk;
  tempDFLL48CalibrationCoarse = tempDFLL48CalibrationCoarse >> FUSES_DFLL48M_COARSE_CAL_Pos;
  // Write the coarse calibration value
  SYSCTRL->DFLLVAL.bit.COARSE = tempDFLL48CalibrationCoarse;
  while (!SYSCTRL->PCLKSR.bit.DFLLRDY);

  uint32_t tempDFLL48CalibrationFine; // used to retrieve DFLL48 coarse calibration value from NVM
  // Get factory calibrated value for "DFLL48M COARSE CAL" from NVM Software Calibration Area
  tempDFLL48CalibrationFine = *(uint32_t*)FUSES_DFLL48M_FINE_CAL_ADDR;
  tempDFLL48CalibrationFine &= FUSES_DFLL48M_FINE_CAL_Msk;
  tempDFLL48CalibrationFine = tempDFLL48CalibrationFine >> FUSES_DFLL48M_FINE_CAL_Pos;
  // Write the coarse calibration value
  SYSCTRL->DFLLVAL.bit.FINE = tempDFLL48CalibrationFine;
  while (!SYSCTRL->PCLKSR.bit.DFLLRDY);

  // Switch DFLL48M to Closed Loop mode and enable WAITLOCK
  SYSCTRL->DFLLCTRL.reg |= (uint16_t) (SYSCTRL_DFLLCTRL_MODE | SYSCTRL_DFLLCTRL_WAITLOCK);

  // Now that DFLL48M is running, switch CLKGEN0 source to it to run the core at 48 MHz.
  // Enable output of Generic Clock Generator 0 (GCLK_MAIN) to the GCLK_IO[0] GPIO Pin
  GCLK->GENCTRL.bit.RUNSTDBY = 0;   // Generic Clock Generator is stopped in stdby
  GCLK->GENCTRL.bit.DIVSEL   = 0;   // Use GENDIV.DIV value to divide the generator
  GCLK->GENCTRL.bit.OE       = 0;   // Enable generator output to GCLK_IO[0] (so we can output it to pin, setup below)
  GCLK->GENCTRL.bit.OOV      = 0;   // GCLK_IO[0] output value when generator is off
  GCLK->GENCTRL.bit.IDC      = 1;   // Generator duty cycle is 50/50
  GCLK->GENCTRL.bit.GENEN    = 1;   // Enable the generator
  //The next two lines are where we set the system clock
  GCLK->GENCTRL.bit.SRC = CLOCK_DFLL48;   // Generator source: DFLL48M output
  GCLK->GENCTRL.bit.ID = GENERIC_CLOCK_GENERATOR_0; // Generic clock gen 0 is used for system clock
  // GENCTRL is Write-Synchronized...so wait for write to complete
  while (GCLK->STATUS.bit.SYNCBUSY);

  /*   //Generic clock 2 for ADC - Custom
    GCLK->GENDIV.reg |= GCLK_GENDIV_DIV(4) | GCLK_GENDIV_ID(GENERIC_CLOCK_GENERATOR_2); //set divide factor for gen clock 1

    // Configure Generic Clock Generator 2 with CLOCK_DFLL48 as source
    GCLK->GENCTRL.bit.RUNSTDBY = 0;                         // Generic Clock Generator is stopped in stdby
    GCLK->GENCTRL.bit.DIVSEL   = 0;                         // enable clock divide
    GCLK->GENCTRL.bit.OE       = 0;                         // Disable generator output to GCLK_IO[1]
    GCLK->GENCTRL.bit.OOV      = 0;                         // We will not use this signal as an output
    GCLK->GENCTRL.bit.IDC      = 1;                         // Generator duty cycle is 50/50
    GCLK->GENCTRL.bit.GENEN    = 1;                         // Enable the generator
    GCLK->GENCTRL.bit.SRC      = CLOCK_DFLL48;              // Generator source: DFLL48 output
    GCLK->GENCTRL.bit.ID       = GENERIC_CLOCK_GENERATOR_2; // This was created in Definitions.h, refers to generic clock 1
    // GENCTRL is Write-Synchronized...so wait for write to complete
    while(GCLK->STATUS.bit.SYNCBUSY); */

  Clock_Bus_Setup();
}

void Clock_Bus_Setup() {
  //in power management system do not divide system clock down
  PM->CPUSEL.reg  = PM_CPUSEL_CPUDIV_DIV1;
  PM->APBASEL.reg = PM_APBASEL_APBADIV_DIV1_Val;
  PM->APBBSEL.reg = PM_APBBSEL_APBBDIV_DIV1_Val;
  PM->APBCSEL.reg = PM_APBCSEL_APBCDIV_DIV1_Val;

  /* Enable the APB clock for the ADC. */
  //PM->APBCMASK.reg |= PM_APBCMASK_ADC;
}

void aDCSetup() {

  /* Enable GCLK2 for the ADC */
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_ADC;

  /* Wait for bus synchronization. */
  while (GCLK->STATUS.bit.SYNCBUSY) {};

  // Select reference
  REG_ADC_REFCTRL = ADC_REFCTRL_REFSEL_INTVCC1; //set vref for ADC to VCC

  // Average control 1 sample, no right-shift
  REG_ADC_AVGCTRL |= ADC_AVGCTRL_SAMPLENUM_1;

  // Sampling time, no extra sampling half clock-cycles
  REG_ADC_SAMPCTRL = ADC_SAMPCTRL_SAMPLEN(0);

  // Input control and input scan
  REG_ADC_INPUTCTRL |= ADC_INPUTCTRL_GAIN_DIV2 | ADC_INPUTCTRL_MUXNEG_GND | ADC_INPUTCTRL_MUXPOS_PIN0;
  // Wait for synchronization
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY);

  ADC->CTRLB.reg |= ADC_CTRLB_RESSEL_10BIT | ADC_CTRLB_PRESCALER_DIV128 | ADC_CTRLB_FREERUN; //This is where you set the divide factor, note that the divide call has no effect until you change Arduino wire.c
  //Wait for synchronization
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY);

  ADC->WINCTRL.reg = ADC_WINCTRL_WINMODE_DISABLE; // Disable window monitor mode
  while (ADC->STATUS.bit.SYNCBUSY);

  ADC->EVCTRL.reg |= ADC_EVCTRL_STARTEI; //start ADC when event occurs
  while (ADC->STATUS.bit.SYNCBUSY);

  ADC->CTRLA.reg |= ADC_CTRLA_ENABLE; //set ADC to run in standby
  while (ADC->STATUS.bit.SYNCBUSY);

  ADC->SWTRIG.reg = ADC_SWTRIG_START; // Start ADC conversion
  while (ADC->STATUS.bit.SYNCBUSY); // Wait for sync
}

Hi,

If I understand your issue, you are doing a timing check before configuring clocks and after, with results being the same.

I am not totally sure if this is the answer to your issue but the “startup.c” file that runs when the Xiao resets does the following :

  • Enables the XOSC32K clock.
  • Sets XOSC32K as source for Generic Clock Generator 1
  • Sets Generic Clock Generator 1 as source for Generic Clock Multiplexer 0 (DFLL48M reference)
  • Enable DFLL48M clock
  • Switch Generic Clock Generator 0 to DFLL48M. CPU will run at 48MHz
  • Modify prescaler value of OSC8M to have 8MHz
  • Set OSC8M as source for Generic Clock Generator 3

So after restart,
Generic Clock Generator 0 uses DFLL48M. CPU runs at 48MHz.
Generic Clock Generator 1 uses XOSC32K at 32.768KHz
Generic Clock Generator 3 uses OSC8M at 8MHz

This would appear to be exactly the same as your coding I think hence there will be no difference before and after your clock initialisation. It effectively has already been done for you.

Hope that provides some insight if I am correct!

I am playing with the clocks a lot at the moment. For some reason it appears that the XOSC32K runs at 32.773 KHz, not at 32.768. Not particularly useful for accurate long term clocking applications!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.