Link to details on PCBWay Maker Contest: ttps://www.pcbway.com/project/PCB_DESIGN_CONTEST.aspx
//*******************Arduino Code from Video*********************************
/*This code is from a tutorial on the ForceTronics YouTube Channel that talks about speeding up the sample rate on Arduino boards
* that use the SAMD21 microcontroller like the Arduino Zero or MKR series. This code is free and clear for other to use and modify
* at their own risk.
*/
#include "Adafruit_ZeroFFT.h" //adafruit library for FFT
#include <SPI.h>
#include <SD.h>
const long sRate = 300000; //sample rate of ADC
const int16_t dSize = 1024; //used to set number of samples
const byte chipSelect = 38; //used for SPI chip select pin
const byte gClk = 3; //used to define which generic clock we will use for ADC
const byte intPri = 0; //used to set interrupt priority for ADC
const int cDiv = 1; //divide factor for generic clock
const float period = 3.3334; //period of 300k sample rate
String wFile = "ADC_DATA"; //used as file name to store wind and GPS data
volatile int16_t aDCVal[dSize]; //array to hold ADC samples
volatile int count = 0; //tracks how many samples we have collected
bool done = false; //tracks when done writing data to SD card
void setup() {
portSetup(); //setup the ports or pin to make ADC measurement
genericClockSetup(gClk,cDiv); //setup generic clock and routed it to ADC
aDCSetup(); //this function set up registers for ADC, input argument sets ADC reference
setUpInterrupt(intPri); //sets up interrupt for ADC and argument assigns priority
aDCSWTrigger(); //trigger ADC to start free run mode
}
void loop() {
if(count==(dSize-1) and !done) { //if done reading and they have not been written to SD card yet
removeDCOffset(aDCVal, dSize, 8); //this function removes DC offset if you are measuring an AC signal
int16_t fTTVal[dSize]; //array to hold FFT samples
for(int j=0; j<dSize; j++) fTTVal[j] = aDCVal[j]; //copy one array to another array
ZeroFFT(fTTVal,dSize); //calculate FFT and store into array
SD.begin(chipSelect); //start SD card library
File myFile = SD.open((wFile + ".csv"), FILE_WRITE); //open file to write data to CSV file
if (myFile) {
float sTime = 0;
for (int y = 0; y < dSize; y++) { //write each reading to CSV
myFile.print(String(FFT_BIN(y, sRate, dSize))+",");
myFile.print(String((fTTVal[y]))+",");
myFile.print(sTime,5); //write each reading to SD card as string
myFile.print(",");
myFile.println(String(aDCVal[y])+","); //write each reading to SD card as string
sTime = sTime + period; //update signal period info
}
}
myFile.close(); //close file
done = true; //we are done
}
}
//function for configuring ports or pins, note that this will not use the same pin numbering scheme as Arduino
void portSetup() {
// Input pin for ADC Arduino A0/PA02
REG_PORT_DIRCLR1 = PORT_PA02;
// Enable multiplexing on PA02_AIN0 PA03/ADC_VREFA
PORT->Group[0].PINCFG[2].bit.PMUXEN = 1;
PORT->Group[0].PINCFG[3].bit.PMUXEN = 1;
PORT->Group[0].PMUX[1].reg = PORT_PMUX_PMUXE_B | PORT_PMUX_PMUXO_B;
}
//this function sets up the generic clock that will be used for the ADC unit
//by default it uses the 48M system clock, input arguments set divide factor for generic clock and choose which generic clock
//Note unless you understand how the clock system works use clock 3. clocks 5 and up can brick the microcontroller based on how Arduino configures things
void genericClockSetup(int clk, int dFactor) {
// Enable the APBC clock for the ADC
REG_PM_APBCMASK |= PM_APBCMASK_ADC;
//This allows you to setup a div factor for the selected clock certain clocks allow certain division factors: Generic clock generators 3 - 8 8 division factor bits - DIV[7:0]
GCLK->GENDIV.reg |= GCLK_GENDIV_ID(clk)| GCLK_GENDIV_DIV(dFactor);
while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);
//configure the generator of the generic clock with 48MHz clock
GCLK->GENCTRL.reg |= GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL48M | GCLK_GENCTRL_ID(clk); // GCLK_GENCTRL_DIVSEL don't need this, it makes divide based on power of two
while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);
//enable clock, set gen clock number, and ID to where the clock goes (30 is ADC)
GCLK->CLKCTRL.reg |= GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN(clk) | GCLK_CLKCTRL_ID(30);
while (GCLK->STATUS.bit.SYNCBUSY);
}
/*
ADC_CTRLB_PRESCALER_DIV4_Val 0x0u
ADC_CTRLB_PRESCALER_DIV8_Val 0x1u
ADC_CTRLB_PRESCALER_DIV16_Val 0x2u
ADC_CTRLB_PRESCALER_DIV32_Val 0x3u
ADC_CTRLB_PRESCALER_DIV64_Val 0x4u
ADC_CTRLB_PRESCALER_DIV128_Val 0x5u
ADC_CTRLB_PRESCALER_DIV256_Val 0x6u
ADC_CTRLB_PRESCALER_DIV512_Val 0x7u
--> 8 bit ADC measurement takes 5 clock cycles, 10 bit ADC measurement takes 6 clock cycles
--> Using 48MHz system clock with division factor of 1
--> Using ADC division factor of 32
--> Sample rate = 48M / (5 x 32) = 300 KSPS
This function sets up the ADC, including setting resolution and ADC sample rate
*/
void aDCSetup() {
// 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_1X | ADC_INPUTCTRL_MUXNEG_GND | ADC_INPUTCTRL_MUXPOS_PIN0;
// Wait for synchronization
while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY);
ADC->CTRLB.reg |= ADC_CTRLB_RESSEL_8BIT | ADC_CTRLB_PRESCALER_DIV32 | 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);
}
//This function sets up an ADC interrupt that is triggered
//when an ADC value is out of range of the window
//input argument is priority of interrupt (0 is highest priority)
void setUpInterrupt(byte priority) {
ADC->INTENSET.reg |= ADC_INTENSET_RESRDY; // enable ADC ready interrupt
while (ADC->STATUS.bit.SYNCBUSY);
NVIC_EnableIRQ(ADC_IRQn); // enable ADC interrupts
NVIC_SetPriority(ADC_IRQn, priority); //set priority of the interrupt
}
//software trigger to start ADC in free run
//in future could use this to set various ADC triggers
void aDCSWTrigger() {
ADC->SWTRIG.reg |= ADC_SWTRIG_START;
}
//This ISR is called each time ADC makes a reading
void ADC_Handler() {
if(count<1023) {
aDCVal[count] = REG_ADC_RESULT;
count++;
}
ADC->INTFLAG.reg = ADC_INTENSET_RESRDY; //Need to reset interrupt
}
//This function takes out DC offset of AC signal, it assumes that the offset brings signal to zero volts
//input arguments: array with measured points and bits of measurement
void removeDCOffset(volatile int16_t aDC[], int aSize, int bits) {
int aSteps = pow(2,bits)/2; //get number of levels in ADC measurement and cut it in half
for(int i=0; i<aSize; i++) {
aDC[i] = aDC[i] - aSteps; //take out offset
}
}
Hello,
ReplyDeleteGreat post, very informative. I am unsure if you still monitor this site, but figured I would leave this question here just in case.
If I wanted to set-up synchronized 8-bit ADC sampling on 2 channels how would this be handled? Would both 8-bit results populate the 16-bit result register?
For anyone interested you'll have to do some research on input scan and offset. Set up the analog pins in the same fashion except the following line will change based on how much pins you want to sample from:
ReplyDelete// Input control and input scan
REG_ADC_INPUTCTRL |= ADC_INPUTCTRL_GAIN_1X | ADC_INPUTCTRL_MUXNEG_GND | ADC_INPUTCTRL_MUXPOS_PIN4 | ADC_INPUTCTRL_INPUTSCAN(1) | ADC_INPUTCTRL_INPUTOFFSET(0); //PIN4 for A3, scan 1 for A3 and A4
then you can determine which pin is being sampled by reading the input control register value in the ADC_HANDLER()
something like:
active_adc_regiser = REG_ADC_INPUTCTRL;
.. I am still unsure how many cycles it takes to complete a scan, which is quite important to know how fast your are sampling each pin... but this worked for me!
This comment has been removed by the author.
DeleteHi, this is very helpful. I am trying to combine window mode with resrdy as I want to trigger recording a set of readings when a threshold is exceeded.
ReplyDeleteSo I took out the bit in the disable flag in the WINCTRL register, and set a window - both works fine.
When I add the window interrupt to the interrupt setup
ADC->INTENSET.reg |= ADC_INTENSET_RESRDY |ADC_INTENSET_WINMON;
the aDCSWTrigger() does not seem to execute anymore - at least a following Serial.println("ADC started."); does not get printed.
The manual reads as if two interrupts from the same peripheral should be ok.
Any help?
Thanks
The wonder of asking questions... solved:
Deletethe issue was in the ISR, changed to
void ADC_Handler() {
if ( !wintriggered and (ADC->INTFLAG.reg & 0b00000100) >> ADC_INTFLAG_WINMON_Pos == 1 ) {
wintriggered = true;
}
if (wintriggered and count < dSize and (ADC->INTFLAG.reg & 0b00000001) == 1) {
aDCVal[count] = REG_ADC_RESULT;
count++;
}
ADC->INTFLAG.reg |= ADC_INTFLAG_RESRDY;
}
and added a funcation after writing a set of records to SD:
void resetWin() {
count = 0;
wintriggered = false;
ADC->INTFLAG.reg |= ADC_INTFLAG_WINMON;
}