Hey raidersnake,
I came across your post because I'm trying to accomplish a very similar goal on my Feather M4 Express + Datalogger Featherwing. I tried implementing your fix for the SPI/DMA conflicts, but I guess I'm not quite doing it right.
Unless I'm misunderstanding, all I have to do is replace lines 44-46 with the first 3 lines of your last post, and add the last 2 lines into setup() after the while(!Serial);. Then, adjust the references to descriptor_section and wrb to dereference them.
This lets me compile and upload the program. However, according to my liberally strewn print statements, my board freezes on this call of memcpy():
memcpy(&descriptor_section[2], &descriptor, sizeof(descriptor)); // Copy the descriptor to the descriptor section
This freeze happens regardless of the placement or presence of your reinterpret_cast lines, or enabling/disabling DEBUG_SD_BEGIN.
The only other change I've made to your original code is changing SdFat sd to SdFs sd and File file to FsFile file.
Here's my (your) code in case I've missed something:
#include "FreeStack.h"
#include "SdFat.h"
#include "sdios.h"
#define error(s) sd.errorHalt(&Serial, F(s))
const uint8_t SD_CS_PIN = 10;
#define SPI_CLOCK SD_SCK_MHZ(50)
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)
SdFs sd;
FsFile file;
//ToDo these are not actually switching it on or off
#define DEBUG_R0_P0 1
#define DEBUG_R0_P1 1
#define DEBUG_R1_P0 1
#define DEBUG_R1_P1 1
#define DEBUG_SD_BEGIN 0
volatile boolean results0Part0Ready = false;
volatile boolean results0Part1Ready = false;
volatile boolean results1Part0Ready = false;
volatile boolean results1Part1Ready = false;
uint16_t adcResults0[2048]; // ADC results array 0
uint16_t adcResults1[2048]; // ADC results array 1
// 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 (changed to array pointer)
dmacdescriptor (*descriptor_section)[DMAC_CH_NUM] __attribute__ ((aligned (16))); // DMAC channel descriptors (changed to array pointer)
dmacdescriptor descriptor __attribute__ ((aligned (16)));
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
descriptor_section = reinterpret_cast<dmacdescriptor(*)[DMAC_CH_NUM]>(DMAC->BASEADDR.reg); //point array pointer to BASEADDR defined by SD.begin
wrb = reinterpret_cast<dmacdescriptor(*)[DMAC_CH_NUM]>(DMAC->WRBADDR.reg); //point array pointer to WRBADDR defined by SD.begin
#if DEBUG_SD_BEGIN
// Initialize SD card before setting up DMA
if (!sd.begin(SD_CONFIG)) {
Serial.println("SD initialization failed!");
while (1); // Halt if SD card initialization fails
} else {
Serial.println("SD initialization succeeded!");
}
#endif
pinMode(10, OUTPUT); // Initialise the output on D10 for debug purposes
PORT->Group[PORTB].DIRSET.reg = PORT_PB23; // Initialise the output on D11 for debug purposes TODO: make it autoselect based on board
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->BASEADDR.reg = 0x20002550; // Specify the location of the descriptors
// DMAC->WRBADDR.reg = 0x20002C70; // Specify the location of the write back descriptors
DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf); // Enable the DMAC peripheral
Serial.println("DMAC enabled");
// ADC0
DMAC->Channel[2].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL_LVL3; // Set DMAC channel 2 to priority level 3 (highest)
DMAC->Channel[2].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[2]; // 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
Serial.println("1");
Serial.println("1.1");
memcpy(&descriptor_section[2], &descriptor, sizeof(descriptor)); // Copy the descriptor to the descriptor section
Serial.println("1.2");
Serial.println("2");
DMAC->Channel[3].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL_LVL3; // Set DMAC channel 3 to priority level 3 (highest)
Serial.println("3");
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
Serial.println("4");
descriptor.descaddr = (uint32_t)&linked_descriptor[0]; // Set up a circular descriptor
Serial.println("5");
descriptor.srcaddr = (uint32_t)&ADC0->RESULT.reg; // Take the result from the ADC0 RESULT register
Serial.println("6");
descriptor.dstaddr = (uint32_t)adcResults0 + sizeof(uint16_t) * 1024; // Place it in the adcResults0 array
Serial.println("7");
descriptor.btcnt = 1024; // Beat count
Serial.println("8");
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
Serial.println("9");
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[1024] + sizeof(uint16_t) * 1024; // Place it in the adcResults1 array
descriptor.btcnt = 1024; // 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
Serial.println("10");
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
Serial.println("ADC0 setup done");
// 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) * 1024; // Place it in the adcResults0 array
descriptor.btcnt = 1024; // 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[1024] + sizeof(uint16_t) * 1024; // Place it in the adcResults1 array
descriptor.btcnt = 1024; // 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
Serial.println("ADC1 setup done");
// 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[2].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("Setup All Done");
// Set a timer to call debugPrintStatus every 10 seconds TODO configure this. Will use R1P0 for meantime
//Timer1.initialize(10000000); // 10 seconds
//Timer1.attachInterrupt(debugPrintStatus);
}
void loop()
{
Serial.println("Loop Start");
debugPrintStatus();
if (results0Part0Ready) // Display the results in results0 array
{
Serial.println(F("Results0 Part0"));
#if DEBUG_R0_P0
Serial.print("DMA3 SUSP interrupt. CHCTRLA: ");
Serial.println(DMAC->Channel[3].CHCTRLA.reg, HEX);
Serial.print("DMA3 BTCNT: ");
Serial.println((*descriptor_section)[3].btcnt);
Serial.print("DMA3 DSTADDR: ");
Serial.println((uint32_t)(*descriptor_section)[3].dstaddr, HEX);
Serial.print("DMA3 SRCADDR: ");
Serial.println((uint32_t)(*descriptor_section)[3].srcaddr, HEX);
Serial.print("ADC0 Result: ");
Serial.println(ADC0->RESULT.reg);
#endif
Serial.println();
results0Part0Ready = false; // Clear the results0 ready flag
}
if (results0Part1Ready) // Display the results in results1 array
{
Serial.println(F("Results0 Part1"));
#if DEBUG_R0_P1
Serial.print("DMA3 SUSP interrupt. CHCTRLA: ");
Serial.println(DMAC->Channel[3].CHCTRLA.reg, HEX);
Serial.print("DMA3 BTCNT: ");
Serial.println((*descriptor_section)[3].btcnt);
Serial.print("DMA3 DSTADDR: ");
Serial.println((uint32_t)(*descriptor_section)[3].dstaddr, HEX);
Serial.print("DMA3 SRCADDR: ");
Serial.println((uint32_t)(*descriptor_section)[3].srcaddr, HEX);
Serial.print("ADC0 Result: ");
Serial.println(ADC0->RESULT.reg);
#endif
Serial.println();
results0Part1Ready = false; // Clear the results1 ready flag
}
if (results1Part0Ready) // Display the results in results0 array
{
Serial.println(F("Results1 Part0"));
#if DEBUG_R1_P0
Serial.print("DMA4 SUSP interrupt. CHCTRLA: ");
Serial.println(DMAC->Channel[4].CHCTRLA.reg, HEX);
Serial.print("DMA4 BTCNT: ");
Serial.println((*descriptor_section)[4].btcnt);
Serial.print("DMA4 DSTADDR: ");
Serial.println((uint32_t)(*descriptor_section)[4].dstaddr, HEX);
Serial.print("DMA4 SRCADDR: ");
Serial.println((uint32_t)(*descriptor_section)[4].srcaddr, HEX);
Serial.print("ADC1 Result: ");
Serial.println(ADC1->RESULT.reg);
#endif
Serial.println();
results1Part0Ready = false; // Clear the results0 ready flag
}
if (results1Part1Ready) // Display the results in results1 array
{
Serial.println(F("Results1 Part1"));
#if DEBUG_R1_P1
Serial.print("DMA4 SUSP interrupt. CHCTRLA: ");
Serial.println(DMAC->Channel[4].CHCTRLA.reg, HEX);
Serial.print("DMA4 BTCNT: ");
Serial.println((*descriptor_section)[4].btcnt);
Serial.print("DMA4 DSTADDR: ");
Serial.println((uint32_t)(*descriptor_section)[4].dstaddr, HEX);
Serial.print("DMA4 SRCADDR: ");
Serial.println((uint32_t)(*descriptor_section)[4].srcaddr, HEX);
Serial.print("ADC1 Result: ");
Serial.println(ADC1->RESULT.reg);
#endif
Serial.println();
results1Part1Ready = false; // Clear the results1 ready flag
}
}
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
delayMicroseconds(3); // Short delay for visualization
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
PORT->Group[PORTB].OUTSET.reg = PORT_PB23; // Toggle D10 HIGH // Toggle the output high then low on D11 for debug purposes
delayMicroseconds(3); // Short delay for visualization
PORT->Group[PORTB].OUTCLR.reg = PORT_PB23; // Toggle D10 LOW
}
}
void debugPrintStatus() {
// Print out DMAC channel status and flags
Serial.println(F("=== DMAC Channel Status ==="));
for (int i = 0; i < 5; i++) {
Serial.print(F("Channel ")); Serial.println(i);
Serial.print(F(" CHCTRLA: ")); Serial.println(DMAC->Channel[i].CHCTRLA.reg, HEX);
Serial.print(F(" CHCTRLB: ")); Serial.println(DMAC->Channel[i].CHCTRLB.reg, HEX);
Serial.print(F(" CHINTFLAG: ")); Serial.println(DMAC->Channel[i].CHINTFLAG.reg, HEX);
Serial.print(F(" CHINTENSET: ")); Serial.println(DMAC->Channel[i].CHINTENSET.reg, HEX);
Serial.print(F(" CHINTENCLR: ")); Serial.println(DMAC->Channel[i].CHINTENCLR.reg, HEX);
// Print descriptor information
Serial.print(F(" Descriptor Address: ")); Serial.println((uint32_t)&descriptor_section[i], HEX);
Serial.print(F(" BTCNT: ")); Serial.println((*descriptor_section)[i].btcnt);
Serial.print(F(" SRCADDR: ")); Serial.println((uint32_t)(*descriptor_section)[i].srcaddr, HEX);
Serial.print(F(" DSTADDR: ")); Serial.println((uint32_t)(*descriptor_section)[i].dstaddr, HEX);
Serial.print(F(" DESCADDR: ")); Serial.println((uint32_t)(*descriptor_section)[i].descaddr, HEX);
}
// Print ADC status
Serial.println(F("=== ADC Status ==="));
Serial.print(F("ADC0 RESULT: ")); Serial.println(ADC0->RESULT.reg, HEX);
Serial.print(F("ADC0 INTFLAG: ")); Serial.println(ADC0->INTFLAG.reg, HEX);
Serial.print(F("ADC0 STATUS: ")); Serial.println(ADC0->STATUS.reg, HEX);
Serial.print(F("ADC1 RESULT: ")); Serial.println(ADC1->RESULT.reg, HEX);
Serial.print(F("ADC1 INTFLAG: ")); Serial.println(ADC1->INTFLAG.reg, HEX);
Serial.print(F("ADC1 STATUS: ")); Serial.println(ADC1->STATUS.reg, HEX);
// Print other important registers
Serial.println(F("=== Other Important Registers ==="));
Serial.print(F("DMAC BASEADDR: ")); Serial.println(DMAC->BASEADDR.reg, HEX);
Serial.print(F("DMAC WRBADDR: ")); Serial.println(DMAC->WRBADDR.reg, HEX);
Serial.print(F("DMAC CTRL: ")); Serial.println(DMAC->CTRL.reg, HEX);
}
Also, thanks MartinL for indirectly helping me out as well! You're in every relevant thread I've found lol.