Skip to content

Instantly share code, notes, and snippets.

@branw
Last active March 21, 2024 18:52
Show Gist options
  • Save branw/de347d76461a7635eeea7aea84750477 to your computer and use it in GitHub Desktop.
Save branw/de347d76461a7635eeea7aea84750477 to your computer and use it in GitHub Desktop.
Minimum working example of analog data acquisition with one output and two inputs on the SAMD51 Grand Central M4.
// Produces a sawtooth on A0 and samples A1 and A3 into buffers via DMA
#include <Adafruit_ZeroDMA.h>
#include <wiring_private.h> // for access to pinPeripheral
// Create buffers for DMAing data around -- the .dmabuffers section is supposed to
// optimize the memory location for the DMA controller
__attribute__ ((section(".dmabuffers"), used)) static uint16_t dac_buffer[4096], adc_buffer[2][4096];
void setup() {
pinMode(5, OUTPUT);
pinMode(6, OUTPUT);
pinMode(7, OUTPUT);
Serial.begin(1337);
// Populate output buffer
for (auto i = 0; i < 4096; i++) dac_buffer[i] = i % 4096;
clock_init();
// Initialize peripherals
adc_init(A1, ADC0);
adc_init(A3, ADC1);
dac_init();
dma_init();
timer_init();
// Trigger both ADCs to enter free-run mode
ADC0->SWTRIG.bit.START = 1;
ADC1->SWTRIG.bit.START = 1;
}
void loop() {
// The M4's digitalWrite implementation is surprisingly fast -- this loop runs at 2MHz
digitalWrite(5, HIGH);
digitalWrite(5, LOW);
}
void dma_left_complete(Adafruit_ZeroDMA *dma) {
// This is called twice every full block (every 2048 samples, ~2.1ms)
digitalWrite(6, HIGH);
digitalWrite(6, LOW);
}
void dma_right_complete(Adafruit_ZeroDMA *dma) {
// This is called once every full block (every 4096 samples, ~4.2ms)
digitalWrite(7, HIGH);
digitalWrite(7, LOW);
}
void clock_init() {
// Create a new generic 48MHz clock for our timer
GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(1) | // Divide the 48MHz clock source by divisor 1: 48MHz/1 = 48MHz
GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK7
GCLK_GENCTRL_SRC_DFLL; // Select 48MHz DFLL clock source
while (GCLK->SYNCBUSY.bit.GENCTRL7);
// Setup TCC0's clock
GCLK->PCHCTRL[TCC0_GCLK_ID].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN_GCLK7;
// Setup ADC1's clock (ADC0 is pre-configured by Arduino)
GCLK->PCHCTRL[ADC1_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK1_Val | (1 << GCLK_PCHCTRL_CHEN_Pos);
}
void timer_init() {
TCC0->CTRLA.reg = TC_CTRLA_PRESCALER_DIV4 | // Set prescaler to 8, 48MHz/8 = 6MHz
TC_CTRLA_PRESCSYNC_PRESC; // Set the reset/reload to trigger on prescaler clock
TCC0->WAVE.reg = TC_WAVE_WAVEGEN_NPWM; // Set-up TCC0 timer for Normal (single slope) PWM mode (NPWM)
while (TCC0->SYNCBUSY.bit.WAVE);
TCC0->PER.reg = 12; // Set-up the PER (period) register 50Hz PWM
while (TCC0->SYNCBUSY.bit.PER);
TCC0->CC[0].reg = 6; // Set-up the CC (counter compare), channel 0 register for 50% duty-cycle
while (TCC0->SYNCBUSY.bit.CC0);
TCC0->CTRLA.bit.ENABLE = 1; // Enable timer TCC0
while (TCC0->SYNCBUSY.bit.ENABLE);
}
void dma_init() {
static Adafruit_ZeroDMA left_in_dma, right_in_dma, out_dma;
{
left_in_dma.allocate();
auto desc = left_in_dma.addDescriptor(
(void *)(&ADC0->RESULT.reg),
adc_buffer[0],
2048,
DMA_BEAT_SIZE_HWORD,
false,
true);
desc->BTCTRL.bit.BLOCKACT = DMA_BLOCK_ACTION_INT;
desc = left_in_dma.addDescriptor(
(void *)(&ADC0->RESULT.reg),
&adc_buffer[0][2048],
2048,
DMA_BEAT_SIZE_HWORD,
false,
true);
desc->BTCTRL.bit.BLOCKACT = DMA_BLOCK_ACTION_INT;
left_in_dma.loop(true);
left_in_dma.setTrigger(0x44);
left_in_dma.setAction(DMA_TRIGGER_ACTON_BEAT);
left_in_dma.setCallback(dma_left_complete);
left_in_dma.startJob();
}
{
right_in_dma.allocate();
auto desc = right_in_dma.addDescriptor(
(void *)(&ADC1->RESULT.reg),
adc_buffer[1],
4096,
DMA_BEAT_SIZE_HWORD,
false,
true);
desc->BTCTRL.bit.BLOCKACT = DMA_BLOCK_ACTION_INT;
right_in_dma.loop(true);
right_in_dma.setTrigger(0x46);
right_in_dma.setAction(DMA_TRIGGER_ACTON_BEAT);
right_in_dma.setCallback(dma_right_complete);
right_in_dma.startJob();
}
{
out_dma.allocate();
auto desc = out_dma.addDescriptor(
dac_buffer,
(void *)(&DAC->DATA[0]),
4096,
DMA_BEAT_SIZE_HWORD,
true,
false);
desc->BTCTRL.bit.BLOCKACT = DMA_BLOCK_ACTION_INT;
out_dma.loop(true);
out_dma.setTrigger(0x16);
out_dma.setAction(DMA_TRIGGER_ACTON_BEAT);
out_dma.startJob();
}
}
void dac_init() {
// Disable DAC
DAC->CTRLA.bit.ENABLE = 0;
while (DAC->SYNCBUSY.bit.ENABLE || DAC->SYNCBUSY.bit.SWRST);
// Use an external reference voltage (see errata; the internal reference is busted)
DAC->CTRLB.reg = DAC_CTRLB_REFSEL_VREFPB;
while (DAC->SYNCBUSY.bit.ENABLE || DAC->SYNCBUSY.bit.SWRST);
// Enable channel 0
DAC->DACCTRL[0].bit.ENABLE = 1;
while (DAC->SYNCBUSY.bit.ENABLE || DAC->SYNCBUSY.bit.SWRST);
// Enable DAC
DAC->CTRLA.bit.ENABLE = 1;
while (DAC->SYNCBUSY.bit.ENABLE || DAC->SYNCBUSY.bit.SWRST);
}
void adc_init(int inpselCFG, Adc *ADCx) {
// Configure the ADC pin (cheating method)
pinPeripheral(inpselCFG, PIO_ANALOG);
ADCx->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND; // No Negative input (Internal Ground)
while( ADCx->SYNCBUSY.reg & ADC_SYNCBUSY_INPUTCTRL );
ADCx->INPUTCTRL.bit.MUXPOS = g_APinDescription[inpselCFG].ulADCChannelNumber; // Selection for the positive ADC input
while( ADCx->SYNCBUSY.reg & ADC_SYNCBUSY_INPUTCTRL );
ADCx->CTRLA.bit.PRESCALER = ADC_CTRLA_PRESCALER_DIV4_Val;
while( ADCx->SYNCBUSY.reg & ADC_SYNCBUSY_CTRLB );
ADCx->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_12BIT_Val;
while( ADCx->SYNCBUSY.reg & ADC_SYNCBUSY_CTRLB );
ADCx->SAMPCTRL.reg = 0x0;
while( ADCx->SYNCBUSY.reg & ADC_SYNCBUSY_SAMPCTRL );
ADCx->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // 1 sample only (no oversampling nor averaging)
ADC_AVGCTRL_ADJRES(0x0ul); // Adjusting result by 0
while( ADCx->SYNCBUSY.reg & ADC_SYNCBUSY_AVGCTRL );
ADCx->REFCTRL.bit.REFSEL = ADC_REFCTRL_REFSEL_AREFA_Val; // 1/2 VDDANA = 0.5* 3V3 = 1.65V
while(ADCx->SYNCBUSY.reg & ADC_SYNCBUSY_REFCTRL);
ADCx->CTRLB.reg |= 0x02; ; //FREERUN
while( ADCx->SYNCBUSY.reg & ADC_SYNCBUSY_CTRLB);
ADCx->CTRLA.bit.ENABLE = 0x01;
while( ADCx->SYNCBUSY.reg & ADC_SYNCBUSY_ENABLE );
}
@jmc1228
Copy link

jmc1228 commented Aug 13, 2021

Hi Branw, this code is so helpful. Thank you for sharing it. It looks like you are fetching data from two channels, one per ADC. I need to fetch data from 4 channels on each ADC. How would I modify your code to preserve speed efficiencies of DMA and sample from multiple ADC channels? Thanks!

@twillging
Copy link

It appears that only one ADC is recording data into its buffer. Second ADC (buffer[1]) is not properly receiving data

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment