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
}