#include "Arduino.h"
#include "ADC.h"
#include <DMAChannel.h>
// Pin array for 10 sensors
int pins[] = {A0, A1, A2, A3, A4, A5, A6, A7, A8, A9};
float frequency = 5000.0f;
// DMA buffers for triple-buffering ADC results
DMAMEM __attribute__((aligned(32))) static uint16_t adc_buffer_A[10];
DMAMEM __attribute__((aligned(32))) static uint16_t adc_buffer_B[10];
DMAMEM __attribute__((aligned(32))) static uint16_t adc_buffer_C[10];
// Array to hold addresses of the buffers
DMAMEM __attribute__((aligned(32))) static volatile uint16_t* buffer_addresses[3] = {adc_buffer_A, adc_buffer_B, adc_buffer_C};
DMAMEM volatile uint16_t* buffer_status_storage = nullptr;
// DMA channel for ADC_ETC
DMAChannel dma;
// Forward declarations
void adc_init();
void adc_etc_reset();
void adc_etc_setup();
void setupEDMA();
void setup_timer_and_xbar(const float frequency);
void start_timer();
extern "C" {
extern void xbar_connect(unsigned int input, unsigned int output);
}
void setup() {
while (!Serial && millis() < 5000);
Serial.println("Setup ADC");
// ADC settings
ADC *adc = new ADC();
adc->adc0->setAveraging(4);
adc->adc0->setResolution(12);
adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED);
adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED);
adc->adc0->singleMode();
adc->adc1->setAveraging(4);
adc->adc1->setResolution(12);
adc->adc1->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED);
adc->adc1->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED);
adc->adc1->singleMode();
adc_init();
adc_etc_reset();
adc_etc_setup();
setup_timer_and_xbar(frequency); // Set the frequency
setupEDMA(); // Initialize eDMA with triple-buffering configuration
start_timer();
}
void loop() {
static uint16_t local_buffer[10]; // Local buffer to hold the copied data
// Clear the cache for each buffer before accessing the data
arm_dcache_delete((void*)adc_buffer_A, sizeof(adc_buffer_A));
arm_dcache_delete((void*)adc_buffer_B, sizeof(adc_buffer_B));
arm_dcache_delete((void*)adc_buffer_C, sizeof(adc_buffer_C));
// Read the stored buffer address from buffer_status_storage
uint16_t* current_read_buffer = (uint16_t*)buffer_status_storage; // Correct cast without volatile type
// Check if the address is valid (points to one of the known buffers)
if (current_read_buffer != adc_buffer_A &&
current_read_buffer != adc_buffer_B &&
current_read_buffer != adc_buffer_C) {
Serial.println("Invalid buffer address, aborting read.");
delay(1000);
return;
}
// Copy data to local buffer and process it
memcpy(local_buffer, adc_buffer_A, sizeof(local_buffer));
// Process the ADC results from the safe-to-read local buffer
for (int i = 0; i < 10; i++) {
Serial.printf("ADC Result[%d]: %d\n", i, local_buffer[i]);
}
delay(1000); // Delay for demonstration; adjust as needed
}
void adc_init() {
ADC1_CFG |= ADC_CFG_ADTRG; // Hardware trigger
ADC1_HC0 = 16; // ADC_ETC channel, 144 int enabled, 16 int disabled
ADC1_GC &= ~ADC_GC_ADCO; // Continuous conversion disabled, same as adc->adcX->singleMode()
ADC2_CFG |= ADC_CFG_ADTRG; // Hardware trigger
ADC2_HC0 = 16; // ADC_ETC channel
ADC2_GC &= ~ADC_GC_ADCO; // Continuous conversion disabled
}
void adc_etc_reset() {
IMXRT_ADC_ETC.CTRL = ADC_ETC_CTRL_SOFTRST; // SOFTRST
asm volatile("dsb"); // Ensure SOFTRST operation is completed
IMXRT_ADC_ETC.CTRL &= ~ADC_ETC_CTRL_SOFTRST; // Clear SOFTRST
delay(5);
}
void adc_etc_setup() {
const uint8_t chainLength0 = 5; // Chain length for the first 5 sensors (TRIG[0])
const uint8_t chainLength4 = 5; // Chain length for the last 5 sensors (TRIG[4])
const uint8_t trigger_adc_0 = 0; // TRIG[0] for ADC1
const uint8_t trigger_adc_4 = 4; // TRIG[4] for ADC2
// Hard-coded pin assignments for sensors
int adc_0_pinArray[5] = { A0, A1, A2, A3, A4 }; // Pins for ADC1
int adc_1_pinArray[5] = { A5, A6, A7, A8, A9 }; // Pins for ADC2
uint8_t adc_pin_channel_0[5];
uint8_t adc_pin_channel_1[5];
// Map each pin to its ADC channel
for (int i = 0; i < 5; i++) {
adc_pin_channel_0[i] = ADC::channel2sc1aADC0[adc_0_pinArray[i]];
adc_pin_channel_1[i] = ADC::channel2sc1aADC1[adc_1_pinArray[i]];
}
// Enable triggers for TRIG[0] and TRIG[4], and enable DMA mode
IMXRT_ADC_ETC.CTRL = ADC_ETC_CTRL_TRIG_ENABLE((1 << trigger_adc_0) | (1 << trigger_adc_4)) | ADC_ETC_CTRL_DMA_MODE_SEL;
// Configure TRIG[0] for ADC1
IMXRT_ADC_ETC.TRIG[trigger_adc_0].CTRL = ADC_ETC_TRIG_CTRL_TRIG_CHAIN(chainLength0 - 1) | ADC_ETC_TRIG_CTRL_SYNC_MODE;
// Set up each chain in TRIG[0] with DMA-enabled configuration
IMXRT_ADC_ETC.TRIG[trigger_adc_0].CHAIN_1_0 =
ADC_ETC_TRIG_CHAIN_B2B0 |
ADC_ETC_TRIG_CHAIN_IE0(0) |
ADC_ETC_TRIG_CHAIN_HWTS0(1) |
ADC_ETC_TRIG_CHAIN_CSEL0(adc_pin_channel_0[0]) |
ADC_ETC_TRIG_CHAIN_B2B1 |
ADC_ETC_TRIG_CHAIN_IE1(0) |
ADC_ETC_TRIG_CHAIN_HWTS1(1) |
ADC_ETC_TRIG_CHAIN_CSEL1(adc_pin_channel_0[1]);
IMXRT_ADC_ETC.TRIG[trigger_adc_0].CHAIN_3_2 =
ADC_ETC_TRIG_CHAIN_B2B0 |
ADC_ETC_TRIG_CHAIN_IE0(0) |
ADC_ETC_TRIG_CHAIN_HWTS0(1) |
ADC_ETC_TRIG_CHAIN_CSEL0(adc_pin_channel_0[2]) |
ADC_ETC_TRIG_CHAIN_B2B1 |
ADC_ETC_TRIG_CHAIN_IE1(0) |
ADC_ETC_TRIG_CHAIN_HWTS1(1) |
ADC_ETC_TRIG_CHAIN_CSEL1(adc_pin_channel_0[3]);
IMXRT_ADC_ETC.TRIG[trigger_adc_0].CHAIN_5_4 =
ADC_ETC_TRIG_CHAIN_B2B0 |
ADC_ETC_TRIG_CHAIN_IE0(1) |
ADC_ETC_TRIG_CHAIN_HWTS0(1) |
ADC_ETC_TRIG_CHAIN_CSEL0(adc_pin_channel_0[4]);
// Configure TRIG[4] for ADC2
IMXRT_ADC_ETC.TRIG[trigger_adc_4].CTRL = ADC_ETC_TRIG_CTRL_TRIG_CHAIN(chainLength4 - 1) | ADC_ETC_TRIG_CTRL_SYNC_MODE;
// Set up each chain in TRIG[4] with DMA-enabled configuration
IMXRT_ADC_ETC.TRIG[trigger_adc_4].CHAIN_1_0 =
ADC_ETC_TRIG_CHAIN_B2B0 |
ADC_ETC_TRIG_CHAIN_IE0(0) |
ADC_ETC_TRIG_CHAIN_HWTS0(1) |
ADC_ETC_TRIG_CHAIN_CSEL0(adc_pin_channel_1[0]) |
ADC_ETC_TRIG_CHAIN_B2B1 |
ADC_ETC_TRIG_CHAIN_IE1(0) |
ADC_ETC_TRIG_CHAIN_HWTS1(1) |
ADC_ETC_TRIG_CHAIN_CSEL1(adc_pin_channel_1[1]);
IMXRT_ADC_ETC.TRIG[trigger_adc_4].CHAIN_3_2 =
ADC_ETC_TRIG_CHAIN_B2B0 |
ADC_ETC_TRIG_CHAIN_IE0(0) |
ADC_ETC_TRIG_CHAIN_HWTS0(1) |
ADC_ETC_TRIG_CHAIN_CSEL0(adc_pin_channel_1[2]) |
ADC_ETC_TRIG_CHAIN_B2B1 |
ADC_ETC_TRIG_CHAIN_IE1(0) |
ADC_ETC_TRIG_CHAIN_HWTS1(1) |
ADC_ETC_TRIG_CHAIN_CSEL1(adc_pin_channel_1[3]);
IMXRT_ADC_ETC.TRIG[trigger_adc_4].CHAIN_5_4 =
ADC_ETC_TRIG_CHAIN_B2B0 |
ADC_ETC_TRIG_CHAIN_IE0(0) |
ADC_ETC_TRIG_CHAIN_HWTS0(1) |
ADC_ETC_TRIG_CHAIN_CSEL0(adc_pin_channel_1[4]);
// Enable the DMA for the trigger events
IMXRT_ADC_ETC.DMA_CTRL = ADC_ETC_DMA_CTRL_TRIQ_ENABLE(trigger_adc_0) | ADC_ETC_DMA_CTRL_TRIQ_ENABLE(trigger_adc_4);
}
void setupEDMA() {
// ================== Buffer A Configuration ==================
// TCD0: Transfer 5 sensor values from TRIG[0] (ADC1) into Buffer A
DMA_TCD0_SADDR = &IMXRT_ADC_ETC.TRIG[0].RESULT_1_0; // Source address from TRIG[0]
DMA_TCD0_SOFF = 2; // Move 2 bytes after each 16-bit value read
DMA_TCD0_ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); // 16-bit transfer size
DMA_TCD0_NBYTES_MLNO = 2; // Transfer 2 bytes per iteration
DMA_TCD0_SLAST = -10; // No adjustment
DMA_TCD0_DADDR = adc_buffer_A; // Destination address starts at Buffer A
DMA_TCD0_DOFF = 2; // Move 2 bytes to next position in Buffer A
DMA_TCD0_CITER_ELINKNO = 5; // 5 transfers for 5 sensors
DMA_TCD0_BITER_ELINKNO = 5;
DMA_TCD0_DLASTSGA = 0; // No adjustment after completion
DMA_TCD0_CSR = DMA_TCD_CSR_MAJORELINK | DMA_TCD_CSR_MAJORLINKCH(1); // Link to TCD1
// TCD1: Transfer 5 sensor values from TRIG[4] (ADC2) into Buffer A
DMA_TCD1_SADDR = &IMXRT_ADC_ETC.TRIG[4].RESULT_1_0; // Source address from TRIG[4]
DMA_TCD1_SOFF = 2; // Move 2 bytes after each read
DMA_TCD1_ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); // 16-bit transfer size
DMA_TCD1_NBYTES_MLNO = 2; // Transfer 2 bytes per iteration
DMA_TCD1_SLAST = -10; // No adjustment
DMA_TCD1_DADDR = &adc_buffer_A[5]; // Continue writing to second half of Buffer A
DMA_TCD1_DOFF = 2; // Move 2 bytes to next position
DMA_TCD1_CITER_ELINKNO = 5; // 5 transfers for 5 sensors
DMA_TCD1_BITER_ELINKNO = 5;
DMA_TCD1_DLASTSGA = 0; // No adjustment after completion
DMA_TCD1_CSR = DMA_TCD_CSR_MAJORELINK | DMA_TCD_CSR_MAJORLINKCH(2); // Link to TCD2
// TCD2: Write address of Buffer A into buffer_status_storage, then link to Buffer B configuration
DMA_TCD2_SADDR = &buffer_addresses[0]; // Source address: pointer to Buffer A in the array
DMA_TCD2_SOFF = 0; // No offset
DMA_TCD2_ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2); // 32-bit transfer size
DMA_TCD2_NBYTES_MLNO = 4; // Transfer 4 bytes (address)
DMA_TCD2_SLAST = 0; // No adjustment
DMA_TCD2_DADDR = &buffer_status_storage; // Destination address: buffer status storage
DMA_TCD2_DOFF = 0; // No offset
DMA_TCD2_CITER_ELINKNO = 1; // Only one transfer
DMA_TCD2_BITER_ELINKNO = 1;
DMA_TCD2_DLASTSGA = 0; // No adjustment after writing
DMA_TCD2_CSR = DMA_TCD_CSR_MAJORELINK | DMA_TCD_CSR_MAJORLINKCH(3); // Link to TCD3 (Buffer B)
// ================== Buffer B Configuration ==================
// TCD3: Transfer 5 sensor values from TRIG[0] (ADC1) into Buffer B
DMA_TCD3_SADDR = &IMXRT_ADC_ETC.TRIG[0].RESULT_1_0; // Source address from TRIG[0]
DMA_TCD3_SOFF = 2; // Move 2 bytes after each 16-bit value read
DMA_TCD3_ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); // 16-bit transfer size
DMA_TCD3_NBYTES_MLNO = 2; // Transfer 2 bytes per iteration
DMA_TCD3_SLAST = -10; // No adjustment
DMA_TCD3_DADDR = adc_buffer_B; // Destination address starts at Buffer B
DMA_TCD3_DOFF = 2; // Move 2 bytes to next position in Buffer B
DMA_TCD3_CITER_ELINKNO = 5; // 5 transfers for 5 sensors
DMA_TCD3_BITER_ELINKNO = 5;
DMA_TCD3_DLASTSGA = 0; // No adjustment after completion
DMA_TCD3_CSR = DMA_TCD_CSR_MAJORELINK | DMA_TCD_CSR_MAJORLINKCH(4); // Link to TCD4
// TCD4: Transfer 5 sensor values from TRIG[4] (ADC2) into Buffer B
DMA_TCD4_SADDR = &IMXRT_ADC_ETC.TRIG[4].RESULT_1_0; // Source address from TRIG[4]
DMA_TCD4_SOFF = 2; // Move 2 bytes after each read
DMA_TCD4_ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); // 16-bit transfer size
DMA_TCD4_NBYTES_MLNO = 2; // Transfer 2 bytes per iteration
DMA_TCD4_SLAST = -10; // No adjustment
DMA_TCD4_DADDR = &adc_buffer_B[5]; // Continue writing to second half of Buffer B
DMA_TCD4_DOFF = 2; // Move 2 bytes to next position
DMA_TCD4_CITER_ELINKNO = 5; // 5 transfers for 5 sensors
DMA_TCD4_BITER_ELINKNO = 5;
DMA_TCD4_DLASTSGA = 0; // No adjustment after completion
DMA_TCD4_CSR = DMA_TCD_CSR_MAJORELINK | DMA_TCD_CSR_MAJORLINKCH(5); // Link to TCD5
// TCD5: Write address of Buffer B into buffer_status_storage, then link to Buffer C configuration
DMA_TCD5_SADDR = &buffer_addresses[1]; // Source address: pointer to Buffer B in the array
DMA_TCD5_SOFF = 0; // No offset
DMA_TCD5_ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2); // 32-bit transfer size
DMA_TCD5_NBYTES_MLNO = 4; // Transfer 4 bytes (address)
DMA_TCD5_SLAST = 0; // No adjustment
DMA_TCD5_DADDR = &buffer_status_storage; // Destination address: buffer status storage
DMA_TCD5_DOFF = 0; // No offset
DMA_TCD5_CITER_ELINKNO = 1; // Only one transfer
DMA_TCD5_BITER_ELINKNO = 1;
DMA_TCD5_DLASTSGA = 0; // No adjustment after writing
DMA_TCD5_CSR = DMA_TCD_CSR_MAJORELINK | DMA_TCD_CSR_MAJORLINKCH(6); // Link to TCD6 (Buffer C)
// ================== Buffer C Configuration ==================
// TCD6: Transfer 5 sensor values from TRIG[0] (ADC1) into Buffer C
DMA_TCD6_SADDR = &IMXRT_ADC_ETC.TRIG[0].RESULT_1_0; // Source address from TRIG[0]
DMA_TCD6_SOFF = 2; // Move 2 bytes after each 16-bit value read
DMA_TCD6_ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); // 16-bit transfer size
DMA_TCD6_NBYTES_MLNO = 2; // Transfer 2 bytes per iteration
DMA_TCD6_SLAST = -10; // No adjustment
DMA_TCD6_DADDR = adc_buffer_C; // Destination address starts at Buffer C
DMA_TCD6_DOFF = 2; // Move 2 bytes to next position in Buffer C
DMA_TCD6_CITER_ELINKNO = 5; // 5 transfers for 5 sensors
DMA_TCD6_BITER_ELINKNO = 5;
DMA_TCD6_DLASTSGA = 0; // No adjustment after completion
DMA_TCD6_CSR = DMA_TCD_CSR_MAJORELINK | DMA_TCD_CSR_MAJORLINKCH(7); // Link to TCD7
// TCD7: Transfer 5 sensor values from TRIG[4] (ADC2) into Buffer C
DMA_TCD7_SADDR = &IMXRT_ADC_ETC.TRIG[4].RESULT_1_0; // Source address from TRIG[4]
DMA_TCD7_SOFF = 2; // Move 2 bytes after each read
DMA_TCD7_ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); // 16-bit transfer size
DMA_TCD7_NBYTES_MLNO = 2; // Transfer 2 bytes per iteration
DMA_TCD7_SLAST = -10; // No adjustment
DMA_TCD7_DADDR = &adc_buffer_C[5]; // Continue writing to second half of Buffer C
DMA_TCD7_DOFF = 2; // Move 2 bytes to next position
DMA_TCD7_CITER_ELINKNO = 5; // 5 transfers for 5 sensors
DMA_TCD7_BITER_ELINKNO = 5;
DMA_TCD7_DLASTSGA = 0; // No adjustment after completion
DMA_TCD7_CSR = DMA_TCD_CSR_MAJORELINK | DMA_TCD_CSR_MAJORLINKCH(8); // Link to TCD8
// TCD8: Write address of Buffer C into buffer_status_storage, then link back to Buffer A configuration
DMA_TCD8_SADDR = &buffer_addresses[2]; // Source address: pointer to Buffer C in the array
DMA_TCD8_SOFF = 0; // No offset
DMA_TCD8_ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2); // 32-bit transfer size
DMA_TCD8_NBYTES_MLNO = 4; // Transfer 4 bytes (address)
DMA_TCD8_SLAST = -10; // No adjustment
DMA_TCD8_DADDR = &buffer_status_storage; // Destination address: buffer status storage
DMA_TCD8_DOFF = 0; // No offset
DMA_TCD8_CITER_ELINKNO = 1; // Only one transfer
DMA_TCD8_BITER_ELINKNO = 1;
DMA_TCD8_DLASTSGA = 0; // No adjustment after writing
DMA_TCD8_CSR = DMA_TCD_CSR_MAJORELINK | DMA_TCD_CSR_MAJORLINKCH(0); // Link back to TCD0 (Buffer A)
// Enable DMA requests for all channels involved
IMXRT_DMA.ERQ |= (1 << 0);
// Configure the DMAMUX for each channel
DMAMUX_CHCFG0 = DMAMUX_CHCFG_ENBL | DMAMUX_SOURCE_ADC_ETC;
DMAMUX_CHCFG1 = DMAMUX_CHCFG_ENBL | DMAMUX_SOURCE_ADC_ETC;
DMAMUX_CHCFG2 = DMAMUX_CHCFG_ENBL | DMAMUX_SOURCE_ADC_ETC;
DMAMUX_CHCFG3 = DMAMUX_CHCFG_ENBL | DMAMUX_SOURCE_ADC_ETC;
DMAMUX_CHCFG4 = DMAMUX_CHCFG_ENBL | DMAMUX_SOURCE_ADC_ETC;
DMAMUX_CHCFG5 = DMAMUX_CHCFG_ENBL | DMAMUX_SOURCE_ADC_ETC;
DMAMUX_CHCFG6 = DMAMUX_CHCFG_ENBL | DMAMUX_SOURCE_ADC_ETC;
DMAMUX_CHCFG7 = DMAMUX_CHCFG_ENBL | DMAMUX_SOURCE_ADC_ETC;
DMAMUX_CHCFG8 = DMAMUX_CHCFG_ENBL | DMAMUX_SOURCE_ADC_ETC;
}
void setup_timer_and_xbar(const float frequency) {
CCM_CCGR2 |= CCM_CCGR2_XBAR1(CCM_CCGR_ON); // Enable clock for XBAR
// Connect TMR4 Timer output to ADC_ETC Trigger input
xbar_connect(XBARA1_IN_QTIMER4_TIMER0, XBARA1_OUT_ADC_ETC_TRIG00); // Connect Timer to trigger TRIG[0]
}
void start_timer() {
// Configure TMR4 Timer directly
TMR4_ENBL &= ~(1 << 0); // Disable Timer
TMR4_SCTRL0 = TMR_SCTRL_OEN | TMR_SCTRL_FORCE;
TMR4_CSCTRL0 = TMR_CSCTRL_CL1(1) | TMR_CSCTRL_TCF1EN;
TMR4_CNTR0 = 0;
TMR4_LOAD0 = 0;
TMR4_COMP10 = (float)F_BUS_ACTUAL / frequency / 2; // Set compare value based on desired frequency
TMR4_CMPLD10 = (float)F_BUS_ACTUAL / frequency / 2; // Set compare value
TMR4_CTRL0 = TMR_CTRL_CM(1) | TMR_CTRL_PCS(8) | TMR_CTRL_LENGTH | TMR_CTRL_OUTMODE(3); // Timer configuration
TMR4_ENBL |= (1 << 0); // Enable Timer
// Debug output to verify timer setup
Serial.printf("Timer started with calculated period: %lu ticks\n", (uint32_t)(F_BUS_ACTUAL / (frequency * 2)));
Serial.flush();
}