I have a data logger that takes in inputs from 4 ADCs and writes them to 2 circular buffers using DMA. This part works well. I am able to read data in and get it to the buffers at about 2.4 Mb/s.
The issue I was running into was how to store it. I eventually figured out how to use SdFat with the SPI configuration that utilizes adafruit's zeroDMA library to handle the SD card writes. I was able to write buffers to the SD card at about 2.7 Mb/s in my stand alone test code. This should be fast enough to keep up with the DMA data logger.
The issues arose when I tried to combine the two. Have data incoming over DMA get written to the SD card. The DMA initialization and setup all works perfectly until sd.begin(SD_CONFIG) is called. I am not sure what is going wrong or what is conflicting in the SdFat library with my DMA logger.
Any help would be great.
// WIP: Use SAMD51's DMA Sequencing to read A0, A1, A3, and A4 on ADC0 and read A2 on ADC1 without CPU intervention and write them to an SD Card. Optional AC trigger and DAC sine wave generator
#include "FreeStack.h"
#include "SdFat.h"
#include "sdios.h"
#define error(s) sd.errorHalt(&Serial, F(s))
const uint8_t SD_CS_PIN = 5;
#define SPI_CLOCK SD_SCK_MHZ(50)
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)
SdFat sd;
File file;
const size_t BUF_SIZE = 512;
volatile boolean results0Part0Ready = false;
volatile boolean results0Part1Ready = false;
volatile boolean results1Part0Ready = false;
volatile boolean results1Part1Ready = false;
uint16_t adcResults0[BUF_SIZE*2]; // ADC results array 0
uint16_t adcResults1[BUF_SIZE*2]; // ADC results array 1
uint16_t r0p0[BUF_SIZE];
uint16_t r0p1[BUF_SIZE];
uint16_t r1p0[BUF_SIZE];
uint16_t r1p1[BUF_SIZE];
// ADC0 INPUTCTRL register MUXPOS settings
uint32_t inputCtrl0[] = { ADC_INPUTCTRL_MUXPOS_AIN0, // AIN0 = A0
ADC_INPUTCTRL_MUXPOS_AIN5, // AIN5 = A1
ADC_INPUTCTRL_MUXPOS_AIN3, // AIN3 = A3
ADC_INPUTCTRL_MUXPOS_AIN4 }; // AIN4 = A4
//uint16_t sintable[256]; // Sine table
typedef struct // DMAC descriptor structure
{
uint16_t btctrl;
uint16_t btcnt;
uint32_t srcaddr;
uint32_t dstaddr;
uint32_t descaddr;
} dmacdescriptor ;
volatile dmacdescriptor wrb[DMAC_CH_NUM] __attribute__ ((aligned (16))); // Write-back DMAC descriptors
dmacdescriptor descriptor_section[DMAC_CH_NUM] __attribute__ ((aligned (16))); // DMAC channel descriptors
dmacdescriptor descriptor __attribute__ ((aligned (16))); // Place holder descriptor
dmacdescriptor linked_descriptor[2] __attribute__ ((aligned (16))); // Linked descriptors
void setup() {
Serial.begin(115200); // Start the native USB port
while(!Serial); // Wait for the console to open
pinMode(10, OUTPUT); // Initialise the output on D10 for debug purposes
pinMode(11, OUTPUT); // Initialise the output on D11 for debug purposes
DMAC->BASEADDR.reg = (uint32_t)descriptor_section; // Specify the location of the descriptors
DMAC->WRBADDR.reg = (uint32_t)wrb; // Specify the location of the write back descriptors
DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf); // Enable the DMAC peripheral
// ADC0
DMAC->Channel[5].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL_LVL3; // Set DMAC channel 2 to priority level 3 (highest)
DMAC->Channel[5].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(ADC0_DMAC_ID_SEQ) | // Set DMAC to trigger on ADC0 DMAC sequence
DMAC_CHCTRLA_TRIGACT_BURST; // DMAC burst transfer
descriptor.descaddr = (uint32_t)&descriptor_section[5]; // Set up a circular descriptor
descriptor.srcaddr = (uint32_t)inputCtrl0 + sizeof(uint32_t) * 4; // Configure the DMAC to set the
descriptor.dstaddr = (uint32_t)&ADC0->DSEQDATA.reg; // Write the INPUT CTRL
descriptor.btcnt = 4; // Beat count is 2
descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_WORD | // Beat size is WORD (32-bits)
DMAC_BTCTRL_SRCINC | // Increment the source address
DMAC_BTCTRL_VALID; // Descriptor is valid
memcpy(&descriptor_section[5], &descriptor, sizeof(descriptor)); // Copy the descriptor to the descriptor section
DMAC->Channel[3].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL_LVL3; // Set DMAC channel 3 to priority level 3 (highest)
DMAC->Channel[3].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(ADC0_DMAC_ID_RESRDY) | // Set DMAC to trigger when ADC0 result is ready
DMAC_CHCTRLA_TRIGACT_BURST; // DMAC burst transfer
descriptor.descaddr = (uint32_t)&linked_descriptor[0]; // Set up a circular descriptor
descriptor.srcaddr = (uint32_t)&ADC0->RESULT.reg; // Take the result from the ADC0 RESULT register
descriptor.dstaddr = (uint32_t)adcResults0 + sizeof(uint16_t) * BUF_SIZE; // Place it in the adcResults0 array
descriptor.btcnt = BUF_SIZE; // Beat count
descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | // Beat size is HWORD (16-bits)
DMAC_BTCTRL_DSTINC | // Increment the destination address
DMAC_BTCTRL_VALID | // Descriptor is valid
DMAC_BTCTRL_BLOCKACT_SUSPEND; // Suspend DMAC channel 3 after block transfer
memcpy(&descriptor_section[3], &descriptor, sizeof(descriptor)); // Copy the descriptor to the descriptor section
descriptor.descaddr = (uint32_t)&descriptor_section[3]; // Set up a circular descriptor
descriptor.srcaddr = (uint32_t)&ADC0->RESULT.reg; // Take the result from the ADC0 RESULT register
descriptor.dstaddr = (uint32_t)&adcResults0[BUF_SIZE] + sizeof(uint16_t) * BUF_SIZE; // Place it in the adcResults1 array
descriptor.btcnt = BUF_SIZE; // Beat count
descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | // Beat size is HWORD (16-bits)
DMAC_BTCTRL_DSTINC | // Increment the destination address
DMAC_BTCTRL_VALID | // Descriptor is valid
DMAC_BTCTRL_BLOCKACT_SUSPEND; // Suspend DMAC channel 3 after block transfer
memcpy(&linked_descriptor[0], &descriptor, sizeof(descriptor)); // Copy the descriptor to the descriptor section
NVIC_SetPriority(DMAC_3_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for DMAC Channel 3
NVIC_EnableIRQ(DMAC_3_IRQn); // Connect DMAC Channel 0 to Nested Vector Interrupt Controller (NVIC)
DMAC->Channel[3].CHINTENSET.reg = DMAC_CHINTENSET_SUSP; // Activate the suspend (SUSP) interrupt on DMAC channel 3
// ADC1
DMAC->Channel[4].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL_LVL3; // Set DMAC channel 4 to priority level 3 (highest)
DMAC->Channel[4].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(ADC1_DMAC_ID_RESRDY) | // Set DMAC to trigger when ADC0 result is ready
DMAC_CHCTRLA_TRIGACT_BURST; // DMAC burst transfer
descriptor.descaddr = (uint32_t)&linked_descriptor[1]; // Set up a circular descriptor
descriptor.srcaddr = (uint32_t)&ADC1->RESULT.reg; // Take the result from the ADC0 RESULT register
descriptor.dstaddr = (uint32_t)adcResults1 + sizeof(uint16_t) * BUF_SIZE; // Place it in the adcResults0 array
descriptor.btcnt = BUF_SIZE; // Beat count
descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | // Beat size is HWORD (16-bits)
DMAC_BTCTRL_DSTINC | // Increment the destination address
DMAC_BTCTRL_VALID | // Descriptor is valid
DMAC_BTCTRL_BLOCKACT_SUSPEND; // Suspend DMAC channel 4 after block transfer
memcpy(&descriptor_section[4], &descriptor, sizeof(descriptor)); // Copy the descriptor to the descriptor section
descriptor.descaddr = (uint32_t)&descriptor_section[4]; // Set up a circular descriptor
descriptor.srcaddr = (uint32_t)&ADC1->RESULT.reg; // Take the result from the ADC0 RESULT register
descriptor.dstaddr = (uint32_t)&adcResults1[BUF_SIZE] + sizeof(uint16_t) * BUF_SIZE; // Place it in the adcResults1 array
descriptor.btcnt = BUF_SIZE; // Beat count
descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | // Beat size is HWORD (16-bits)
DMAC_BTCTRL_DSTINC | // Increment the destination address
DMAC_BTCTRL_VALID | // Descriptor is valid
DMAC_BTCTRL_BLOCKACT_SUSPEND; // Suspend DMAC channel 4 after block transfer
memcpy(&linked_descriptor[1], &descriptor, sizeof(descriptor)); // Copy the descriptor to the descriptor section
NVIC_SetPriority(DMAC_4_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for DMAC Channel 4
NVIC_EnableIRQ(DMAC_4_IRQn); // Connect DMAC Channel 0 to Nested Vector Interrupt Controller (NVIC)
DMAC->Channel[4].CHINTENSET.reg = DMAC_CHINTENSET_SUSP; // Activate the suspend (SUSP) interrupt on DMAC channel 0
// ADC Register Settings
ADC1->INPUTCTRL.bit.MUXPOS = ADC_INPUTCTRL_MUXPOS_AIN0_Val; // Set the analog input to A2
while(ADC1->SYNCBUSY.bit.INPUTCTRL); // Wait for synchronization
ADC1->SAMPCTRL.bit.SAMPLEN = 0x00; // Extend sampling time by SAMPCTRL ADC cycles (12 + 1 + 2)/750kHz = 20us = 50kHz
while(ADC1->SYNCBUSY.bit.SAMPCTRL); // Wait for synchronization
ADC1->DSEQCTRL.reg = ADC_DSEQCTRL_AUTOSTART | // Auto start a DMAC conversion upon ADC0 DMAC sequence completion
ADC_DSEQCTRL_INPUTCTRL; // Change the ADC0 INPUTCTRL register on DMAC sequence
ADC1->CTRLB.reg = ADC_CTRLB_RESSEL_8BIT;// | // Set ADC resolution to 8 bits
// ADC_CTRLB_FREERUN; // Set ADC to free run mode
while(ADC1->SYNCBUSY.bit.CTRLB); // Wait for synchronization
ADC1->CTRLA.bit.SLAVEEN = 1; // Set ADC1 to slave, ADC0 to master, both share CTRLA register
ADC0->INPUTCTRL.bit.MUXPOS = 0x0; // Set the analog input to A0
while(ADC0->SYNCBUSY.bit.INPUTCTRL); // Wait for synchronization
ADC0->SAMPCTRL.bit.SAMPLEN = 0x00; // Extend sampling time by SAMPCTRL ADC cycles (12 + 1 + 2)/750kHz = 20us = 50kHz
while(ADC0->SYNCBUSY.bit.SAMPCTRL); // Wait for synchronization
ADC0->DSEQCTRL.reg = ADC_DSEQCTRL_AUTOSTART | // Auto start a DMAC conversion upon ADC0 DMAC sequence completion
ADC_DSEQCTRL_INPUTCTRL; // Change the ADC0 INPUTCTRL register on DMAC sequence
ADC0->CTRLB.reg = ADC_CTRLB_RESSEL_8BIT;// | // Set ADC resolution to 8 bits
//ADC_CTRLB_FREERUN; // Set ADC to free run mode
while(ADC0->SYNCBUSY.bit.CTRLB); // Wait for synchronization
ADC0->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV4; // Divide Clock ADC GCLK by 64 (48MHz/64 = 750kHz) (12 + 1)/750kHz = 17.3us sample time
ADC0->CTRLA.bit.ENABLE = 1; // Enable the ADC
while(ADC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
ADC0->SWTRIG.bit.START = 1; // Initiate a software trigger to start an ADC conversion
while(ADC0->SYNCBUSY.bit.SWTRIG); // Wait for synchronization
DMAC->Channel[5].CHCTRLA.bit.ENABLE = 1; // Enable DMAC channel 2
DMAC->Channel[3].CHCTRLA.bit.ENABLE = 1; // Enable DMAC channel 3
DMAC->Channel[4].CHCTRLA.bit.ENABLE = 1; // Enable DMAC channel 4
delay(1); // Wait a millisecond
Serial.println(F("Type any character to start"));
while (!Serial.available()){
yield();
}
sd.begin(SD_CONFIG);
file.open("data.dat", O_RDWR | O_CREAT | O_TRUNC | O_APPEND);
for(int i = 0; i < 10; i++){
Serial.print(F("Iteration: "));
Serial.println(i+1);
//----------------------------------------
//----------------------------------------
if (results0Part0Ready) // Display the results in results0 array
{
Serial.println(F("Results0 Part0: "));
for (uint32_t i = 0; i < BUF_SIZE; i++) {
r0p0[i] = adcResults0[i];
Serial.print(i);
Serial.print(F(": "));
Serial.println(adcResults0[i]);
}
if (file.write(r0p0, BUF_SIZE) != BUF_SIZE) {
error("write failed");
}
file.sync();
Serial.println(F("Successfully Written"));
results0Part0Ready = false; // Clear the results0 ready flag
}
else {
Serial.println(F("--- RESULTS 00 NOT READY ---"));
}
//----------------------------------------
//----------------------------------------
if (results0Part1Ready) // Display the results in results1 array
{
Serial.println(F("Results0 Part1: "));
for (uint32_t i = BUF_SIZE; i < BUF_SIZE*2; i++) {
r0p1[i-BUF_SIZE] = adcResults0[i];
Serial.print(i);
Serial.print(F(": "));
Serial.println(adcResults0[i]);
}
if (file.write(r0p1, BUF_SIZE) != BUF_SIZE) {
error("write failed");
}
file.sync();
Serial.println(F("Successfully Written"));
results0Part1Ready = false; // Clear the results1 ready flag
}
else {
Serial.println(F("--- RESULTS 01 NOT READY ---"));
}
//----------------------------------------
//----------------------------------------
if (results1Part0Ready) // Display the results in results0 array
{
Serial.println(F("Results1 Part0: "));
for (uint32_t i = 0; i < BUF_SIZE; i++) {
r1p0[i] = adcResults1[i];
Serial.print(i);
Serial.print(F(": "));
Serial.println(adcResults1[i]);
}
if (file.write(r1p0, BUF_SIZE) != BUF_SIZE) {
error("write failed");
}
file.sync();
Serial.println(F("Successfully Written"));
results1Part0Ready = false; // Clear the results0 ready flag
}
else {
Serial.println(F("--- RESULTS 10 NOT READY ---"));
}
//----------------------------------------
//----------------------------------------
if (results1Part1Ready) // Display the results in results1 array
{
Serial.println(F("Results1 Part1: "));
for (uint32_t i = BUF_SIZE; i < BUF_SIZE*2; i++) {
r1p1[i-BUF_SIZE] = adcResults1[i];
Serial.print(i);
Serial.print(F(": "));
Serial.println(adcResults1[i]);
}
if (file.write(r1p1, BUF_SIZE) != BUF_SIZE) {
error("write failed");
}
file.sync();
Serial.println(F("Successfully Written"));
results1Part1Ready = false; // Clear the results1 ready flag
}
else {
Serial.println(F("--- RESULTS 11 NOT READY ---"));
}
//----------------------------------------
//----------------------------------------
}
file.close();
sd.end();
}
void loop()
{
}
void DMAC_3_Handler() // Interrupt handler for DMAC channel 3
{
static uint8_t count0 = 0; // Initialise the count
if (DMAC->Channel[3].CHINTFLAG.bit.SUSP) // Check if DMAC channel 3 has been suspended (SUSP)
{
DMAC->Channel[3].CHCTRLB.reg = DMAC_CHCTRLB_CMD_RESUME; // Restart the DMAC on channel 3
DMAC->Channel[3].CHINTFLAG.bit.SUSP = 1; // Clear the suspend (SUSP)interrupt flag
if (count0) // Test if the count0 is 1
{
results0Part1Ready = true; // Set the results 0 part 1 ready flag
}
else
{
results0Part0Ready = true; // Set the results 0 part 0 ready flag
}
count0 = (count0 + 1) % 2; // Toggle the count0 between 0 and 1
digitalWrite(10, HIGH); // Toggle the output high then low on D10 for debug purposes
digitalWrite(10, LOW);
}
}
void DMAC_4_Handler() // Interrupt handler for DMAC channel 4
{
static uint8_t count1 = 0; // Initialise the count
if (DMAC->Channel[4].CHINTFLAG.bit.SUSP) // Check if DMAC channel 4 has been suspended (SUSP)
{
DMAC->Channel[4].CHCTRLB.reg = DMAC_CHCTRLB_CMD_RESUME; // Restart the DMAC on channel 4
DMAC->Channel[4].CHINTFLAG.bit.SUSP = 1; // Clear the suspend (SUSP)interrupt flag
if (count1) // Test if the count1 is 1
{
results1Part1Ready = true; // Set the results 1 part 1 ready flag
}
else
{
results1Part0Ready = true; // Set the results 1 part 0 ready flag
}
count1 = (count1 + 1) % 2; // Toggle the count1 between 0 and 1
digitalWrite(11, HIGH); // Toggle the output high then low on D11 for debug purposes
digitalWrite(11, LOW);
}
}