Issues while using ADC with DMA(Photon Audio3 example) on E Series

Hello all I have been trying to use DMA and ADC (Independent Mode) on E series.

I went through rickkas7’s photoAudio3 sample program and tried to implement it. My requirement is very similar to this example.

I have 8 KHz square wave coming in to the A1 pin and I want to sample it at 16KHz frequency (sample both high and low values of the signal). I tried to run timer 3 at 16KHz and changed values in the timer initialization that triggers ADC conversion.

If I run the ADC on Dual Simultaneous Mode, I do get some values via DMA but, as sampling frequency is 16KHz, the sampled 12 bit ADC values should be 4095-0-4095-0(Ideally) and so on.

The issue is that I do not get these expected values. I get some random values like 4095-4095-0-0-0-4095-4095-4095-4095-0-4095(This is totally random and changes every cycle).

As per my understanding, this issue could be due to one of these reasons:

  • Incorrect Timer 3 frequency
  • ADC Dual Simultaneous Mode with DMA Access Mode 1

I think timer 3 frequency and initialization is correct and something is wrong with ADC Dual Simultaneous Mode. I tried to change ADC configuration to the Independent Mode but, I am confused about other parameters that needs to be changed(DMA Access Mode, DMA Peripheral Address etc). Reference Manuel is also confusing for me.

I can use ADC in any mode as long as I get it to work. Can pros out there please throw some light on this?

Thanks!

Here’s my code below(Ignore syntax errors, I might have messed it up while writing this post):

#include "Particle.h"

//
// ADCDMA - Class to use Photon ADC in DMA Mode
//
#include "adc_hal.h"
#include "gpio_hal.h"
#include "pinmap_hal.h"
#include "pinmap_impl.h"

SYSTEM_THREAD(ENABLED);

void buttonHandler(system_event_t event, int data); 

const size_t SAMPLE_BUF_SIZE = 100;

const int SAMPLE_PIN = A1;

const long SAMPLE_RATE = 16000;

uint16_t samples[SAMPLE_BUF_SIZE];

enum State { STATE_WAITING, STATE_CONNECT, STATE_RUNNING, STATE_FINISH };
State state = STATE_WAITING;


class ADCDMA {
public:
	ADCDMA(int pin, uint16_t *buf, size_t bufSize);
	virtual ~ADCDMA();

	void start(size_t freqHZ);
	void stop();

private:
	int pin;
	uint16_t *buf;
	size_t bufSize;
};

ADCDMA::ADCDMA(int pin, uint16_t *buf, size_t bufSize) : pin(pin), buf(buf), bufSize(bufSize) {
}

ADCDMA::~ADCDMA() {

}

void ADCDMA::start(size_t freqHZ) {

    // Using Dual ADC Regular Simultaneous DMA Mode 1

	// Using Timer3. To change timers, make sure you edit all of:
	// RCC_APB1Periph_TIM3, TIM3, ADC_ExternalTrigConv_T3_TRGO

	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

	// Set the pin as analog input
	// GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
	// GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    HAL_Pin_Mode(pin, AN_INPUT);

	// Enable the DMA Stream IRQ Channel
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	// 60000000UL = 60 MHz Timer Clock = HCLK / 2
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
	TIM_TimeBaseStructure.TIM_Period = (60000000UL / freqHZ) - 1;
	TIM_TimeBaseStructure.TIM_Prescaler = 0;
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
	TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update); // ADC_ExternalTrigConv_T3_TRGO
	
	TIM_Cmd(TIM3, ENABLE);

	ADC_CommonInitTypeDef ADC_CommonInitStructure;
	ADC_InitTypeDef ADC_InitStructure;
	DMA_InitTypeDef DMA_InitStructure;

	// DMA2 Stream0 channel0 configuration
	DMA_InitStructure.DMA_Channel = DMA_Channel_0;
	DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buf;
	DMA_InitStructure.DMA_PeripheralBaseAddr = 0x40012308; // CDR_ADDRESS; Packed ADC1, ADC2
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
	DMA_InitStructure.DMA_BufferSize = bufSize;
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;
	DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
	DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
	DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
	DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
	DMA_Init(DMA2_Stream0, &DMA_InitStructure);	

	DMA_Cmd(DMA2_Stream0, ENABLE);

	// ADC Common Init
	ADC_CommonInitStructure.ADC_Mode = ADC_DualMode_RegSimult;
	ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
	ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_1;
	ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_20Cycles;
	ADC_CommonInit(&ADC_CommonInitStructure);	

	// ADC1 configuration
	ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
	ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
	ADC_InitStructure.ADC_NbrOfConversion = 1;
	
	ADC_Init(ADC1, &ADC_InitStructure);	
	ADC_Init(ADC2, &ADC_InitStructure);

	STM32_Pin_Info* PIN_MAP = HAL_Pin_Map();
	ADC_RegularChannelConfig(ADC1, PIN_MAP[pin].adc_channel, 1, ADC_SampleTime_15Cycles);
    ADC_RegularChannelConfig(ADC2, PIN_MAP[pin].adc_channel, 1, ADC_SampleTime_15Cycles);

	// Enable DMA request after last transfer (Multi-ADC mode)	
	ADC_MultiModeDMARequestAfterLastTransferCmd(ENABLE);

	// Enable ADCs
	ADC_Cmd(ADC1, ENABLE);	
	ADC_Cmd(ADC2, ENABLE);

	ADC_SoftwareStartConv(ADC1);
}

void ADCDMA::stop() {
	// Stop the ADC
	ADC_Cmd(ADC1, DISABLE);
	ADC_Cmd(ADC2, DISABLE);

	DMA_Cmd(DMA2_Stream0, DISABLE);

	// Stop the timer
	TIM_Cmd(TIM3, DISABLE);
}

ADCDMA adcDMA(SAMPLE_PIN, samples, SAMPLE_BUF_SIZE);

void setup() {
	Serial1.begin(9600);
	
	System.on(button_click, buttonHandler);	
	adcDMA.start(SAMPLE_RATE);

}

void loop() {
	switch(state) {
	case STATE_WAITING:		
		break;

	case STATE_CONNECT:				
			ADC_SoftwareStartConv(ADC1);			
			Serial1.println("starting");
			state = STATE_RUNNING;		
		break;

	case STATE_RUNNING:			
			if (DMA_GetFlagStatus(DMA2_Stream0, DMA_FLAG_TCIF0)) 
			{
				DMA_ClearFlag(DMA2_Stream0, DMA_FLAG_TCIF0);					
				state = STATE_FINISH;
			}							
		break;

	case STATE_FINISH:		
		for(uint16_t i = 0; i < SAMPLE_BUF_SIZE; i++)
		{
			Serial1.println(samples[i]);
			samples[i] = 0;
		}		
		Serial1.println("stopping");		
		state = STATE_WAITING;
		break;
	}
}

// button handler for the SETUP button, used to toggle recording on and off
void buttonHandler(system_event_t event, int data) {
	switch(state) {
	case STATE_WAITING:
		state = STATE_CONNECT;
		break;	
	}
}


Hi, here is some awesome approach on how to read analog data as fast as possible without involving DMA maybe can help you somehow

DreamER thank you so much for your reply.

I have my code running with analogRead() function which is a software solution. With this function, I have been noticing periodic drop in the ADC readings and I couldn’t figure out the cause. In my opinion there’s something happening in the Device OS core. That’s why I wanted to try DMA method and see if ADC readings are still dropping periodically or not.

I tried implementing the method that you posted in the reply. It does work properly, but still there’s a periodic drop in the ADC reading.

I am using sparkIntervalTimer library to sample the Analog pin at 16KHz. Do you have any idea if this library can cause the drop in the ADC readings?

Hi,
honestly I don’t have any idea (I’m too weak in coding) but if in the code which I mentioned in previous post the AnalogRead() is not used at all (just in setup “to configure pins in HAL well”) so the logically potential candidate is sparkIntervalTimer.

Which Device OS you are running ? Also if it’s possible will be much easier for T/S when you can share your code.
Let me ping The Masters :slight_smile: @rickkas7 @ScruffR @peekay123 Do you guys have any Idea what can cause periodic drop in the ADC reading ? Any “light in the dark” will be appreciated

@aarshkd , the SparkIntervalTimer library generates a hardware timer interrupts that can be used for (mostly) deterministic time intervals. At 16KHz, the timer will fire every 62.5 microseconds, assuming nothing in the DeviceOS has preempted it. Taking into account the ISR response overhead, you can assume that you have less than 52 uS to take an analog sample and whatever else you are doing in the ISR. If the ISR takes longer than that, an interrupt will be missed and the ISR will not fire. The DMA approach used by @rickkas7 is a better approach than using SparkIntervalTimer.

The code you posted (adapted from @rickkas7’s sampling code) is running at 16KHz which is the MINIMUM Nyquist sampling interval. However, this does no guarantee a “clean” sampling of your 8 KHz input as the sampling clock is not synchronized with the input. The data you got may well be correct! You really need a minimum x4 sampling rate or 32KHz to get the values you are expecting. In Rick’s code, he assumes a 4KHz (audio) input range which he samples at x8 or 32KHz.

1 Like

@dreamER and @peekay123 thanks!

While using SparkIntervalTImer library, I have made sure that ISR takes way less time than 52uS.

I also prefer using DMA as it’s more efficient and saves CPU cycles. I will write some test codes(Including higher sampling rate for my 8kHz input) and get the results. The only think I am worried about is time sync. between output and input. It will be interesting to see the results.

I will get back to you guys. Thanks again!

There is a bug in the audio3.cpp sample. If you change SAMPLE_PIN it’s not used because a line below should read:

ADCDMA adcDMA(SAMPLE_PIN, samples, SAMPLE_BUF_SIZE);

Instead of using SAMPLE_PIN it’s hardcoded to A0.

I took the audio3.cpp sample and modified it to store the output in a separate larger buffer, then write the samples to USB serial. Here’s the code:

#include "Particle.h"


SYSTEM_MODE(MANUAL);

//
// ADCDMA - Class to use Photon ADC in DMA Mode
//
#include "adc_hal.h"
#include "gpio_hal.h"
#include "pinmap_hal.h"
#include "pinmap_impl.h"


void buttonHandler(system_event_t event, int data); // forward declaration

// 512 is a good size for this buffer. This is the number of samples; the number of bytes is twice
// this, but only half the buffer is handled a time, and then each pair of samples is averaged.
const size_t SAMPLE_BUF_SIZE = 512;

// This is the pin the microphone is connected to.
const int SAMPLE_PIN = A1;

// The audio sample rate. The minimum is probably 8000 for minimally acceptable audio quality.
// Not sure what the maximum rate is, but it's pretty high.
const long SAMPLE_RATE = 16000;

uint16_t samples[SAMPLE_BUF_SIZE];

// This is the size of the output buffer in 16-bit words (with 12-bit samples in it). This must
// be small enough to fit in available RAM. 
const size_t OUTPUT_BUF_SIZE = 16000;

uint16_t output[OUTPUT_BUF_SIZE];
size_t outputIndex = 0;
size_t printIndex = 0;


enum State { STATE_WAITING, STATE_START, STATE_RUNNING, STATE_FINISH, STATE_PRINT };
State state = STATE_WAITING;

//
//
//
class ADCDMA {
public:
	ADCDMA(int pin, uint16_t *buf, size_t bufSize);
	virtual ~ADCDMA();

	void start(size_t freqHZ);
	void stop();

private:
	int pin;
	uint16_t *buf;
	size_t bufSize;
};

// Helpful post:
// https://my.st.com/public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/Flat.aspx?RootFolder=https%3a%2f%2fmy%2est%2ecom%2fpublic%2fSTe2ecommunities%2fmcu%2fLists%2fcortex%5fmx%5fstm32%2fstm32f207%20ADC%2bTIMER%2bDMA%20%20Poor%20Peripheral%20Library%20Examples&FolderCTID=0x01200200770978C69A1141439FE559EB459D7580009C4E14902C3CDE46A77F0FFD06506F5B&currentviews=6249

ADCDMA::ADCDMA(int pin, uint16_t *buf, size_t bufSize) : pin(pin), buf(buf), bufSize(bufSize) {
}

ADCDMA::~ADCDMA() {

}

void ADCDMA::start(size_t freqHZ) {

    // Using Dual ADC Regular Simultaneous DMA Mode 1

	// Using Timer3. To change timers, make sure you edit all of:
	// RCC_APB1Periph_TIM3, TIM3, ADC_ExternalTrigConv_T3_TRGO

	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

	// Set the pin as analog input
	// GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
	// GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    HAL_Pin_Mode(pin, AN_INPUT);

	// Enable the DMA Stream IRQ Channel
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	// 60000000UL = 60 MHz Timer Clock = HCLK / 2
	// Even low audio rates like 8000 Hz will fit in a 16-bit counter with no prescaler (period = 7500)
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
	TIM_TimeBaseStructure.TIM_Period = (60000000UL / freqHZ) - 1;
	TIM_TimeBaseStructure.TIM_Prescaler = 0;
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
	TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update); // ADC_ExternalTrigConv_T3_TRGO
	TIM_Cmd(TIM3, ENABLE);

	ADC_CommonInitTypeDef ADC_CommonInitStructure;
	ADC_InitTypeDef ADC_InitStructure;
	DMA_InitTypeDef DMA_InitStructure;

	// DMA2 Stream0 channel0 configuration
	DMA_InitStructure.DMA_Channel = DMA_Channel_0;
	DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buf;
	DMA_InitStructure.DMA_PeripheralBaseAddr =  0x40012308; // CDR_ADDRESS; Packed ADC1, ADC2;
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
	DMA_InitStructure.DMA_BufferSize = bufSize;
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;
	DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
	DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
	DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
	DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
	DMA_Init(DMA2_Stream0, &DMA_InitStructure);

	// Don't enable DMA Stream Half / Transfer Complete interrupt
	// Since we want to write out of loop anyway, there's no real advantage to using the interrupt, and as
	// far as I can tell, you can't set the interrupt handler for DMA2_Stream0 without modifying
	// system firmware because there's no built-in handler for it.
	// DMA_ITConfig(DMA2_Stream0, DMA_IT_TC | DMA_IT_HT, ENABLE);

	DMA_Cmd(DMA2_Stream0, ENABLE);

	// ADC Common Init
	ADC_CommonInitStructure.ADC_Mode = ADC_DualMode_RegSimult;
	ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
	ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_1;
	ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
	ADC_CommonInit(&ADC_CommonInitStructure);

	// ADC1 configuration
	ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
	ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Left;
	ADC_InitStructure.ADC_NbrOfConversion = 1;
	ADC_Init(ADC1, &ADC_InitStructure);

	// ADC2 configuration - same
	ADC_Init(ADC2, &ADC_InitStructure);

	//
    Hal_Pin_Info* PIN_MAP = HAL_Pin_Map();
	ADC_RegularChannelConfig(ADC1, PIN_MAP[pin].adc_channel, 1, ADC_SampleTime_15Cycles);
    ADC_RegularChannelConfig(ADC2, PIN_MAP[pin].adc_channel, 1, ADC_SampleTime_15Cycles);
    Serial.printlnf("using pin %d ADC channel %u", pin, PIN_MAP[pin].adc_channel);

	// Enable DMA request after last transfer (Multi-ADC mode)
	ADC_MultiModeDMARequestAfterLastTransferCmd(ENABLE);

	// Enable ADCs
	ADC_Cmd(ADC1, ENABLE);
	ADC_Cmd(ADC2, ENABLE);

	ADC_SoftwareStartConv(ADC1);
}

void ADCDMA::stop() {
	// Stop the ADC
	ADC_Cmd(ADC1, DISABLE);
	ADC_Cmd(ADC2, DISABLE);

	DMA_Cmd(DMA2_Stream0, DISABLE);

	// Stop the timer
	TIM_Cmd(TIM3, DISABLE);
}

ADCDMA adcDMA(SAMPLE_PIN, samples, SAMPLE_BUF_SIZE);

// End ADCDMA


void setup() {
	Serial.begin(9600);

	// Register handler to handle clicking on the SETUP button
	System.on(button_click, buttonHandler);
	pinMode(D7, OUTPUT);

}

void loop() {
	uint16_t *sendBuf = NULL;

	switch(state) {
	case STATE_WAITING:
		// Waiting for the user to press the SETUP button. The setup button handler
		// will bump the state into STATE_START
		break;

	case STATE_START:
        Serial.println("starting");
        outputIndex = 0;
        digitalWrite(D7, HIGH);

        adcDMA.start(SAMPLE_RATE);

        state = STATE_RUNNING;
		break;

	case STATE_RUNNING:
		if (DMA_GetFlagStatus(DMA2_Stream0, DMA_FLAG_HTIF0)) {
		    DMA_ClearFlag(DMA2_Stream0, DMA_FLAG_HTIF0);
		    sendBuf = samples;
		}
		if (DMA_GetFlagStatus(DMA2_Stream0, DMA_FLAG_TCIF0)) {
		    DMA_ClearFlag(DMA2_Stream0, DMA_FLAG_TCIF0);
		    sendBuf = &samples[SAMPLE_BUF_SIZE / 2];
		}

		if (sendBuf != NULL) {
			// There is a sample buffer to send

			// Average the pairs of samples and copy to the output buffer
			for(size_t ii = 0; ii < SAMPLE_BUF_SIZE / 2 && outputIndex < OUTPUT_BUF_SIZE; ii += 2) {
				uint32_t sum = (uint32_t)sendBuf[ii] + (uint32_t)sendBuf[ii + 1];
                output[outputIndex++] = (sum / 2);                
            }
		}

		if (outputIndex >= OUTPUT_BUF_SIZE) {
			state = STATE_FINISH;
		}
		break;

	case STATE_FINISH:
		digitalWrite(D7, LOW);
		adcDMA.stop();
		Serial.println("stopping");
        printIndex = 0;
		state = STATE_PRINT;
		break;

    case STATE_PRINT:
        if (printIndex >= outputIndex) {
            Serial.println("done");
            state = STATE_WAITING;
            break;
        }
        Serial.printlnf("%u", output[printIndex++]);
        break;
	}
}

// button handler for the SETUP button, used to toggle recording on and off
void buttonHandler(system_event_t event, int data) {
	switch(state) {
	case STATE_WAITING:
		state = STATE_START;
		break;

	case STATE_RUNNING:
		state = STATE_FINISH;
		break;
	}
}

I ran this on an E Series E402 evaluation board running Device OS 2.0.1.

I fed a 500 Hz sine wave ranging from 0 to around 3V from a function generator into A1.

This is part of the output after tapping the MODE button:

264
920
3752
8520
15048
23384
32296
42936
53920
64720
65520
65520
65520
60720
50864
50752
50688
50560
50760
50880
53272
65520
65520
65520
59184
48416
37424
28016
19144
11888
5800
2008
360
1080
3408
8632
14784
22952
32592
42640
54000
64496
65520
65520
65520
60320
50928
50592
50544
50736
50712
50768
53488
65520
65520
65520
59184
48672
37328
28152
18720
11600
5584
1712
832
688

It seems about right to me. The ADC is configured for left-justified samples so they should be around 65535. I might have bumped the DC offset dial on the function generator because it was approaching 0 on an earlier test run.

2 Likes

Alternatively, if you don’t need a long term continuous sampling, you could add some interrupt triggered sync logic before you start your sampling. This way you could ensure that your 16kHz sampling starts off around the center of your high or low pulse. This way you have the maximum leeway for potential clock drift in either direction before your sample taking happens round about the edges of your signal, causing the potential grief @peekay123 was pointing out.
If you know the “direction” of the drift putting yourself furthest from the edge your sampling tends to drift towards you can extend the acquisition period even further.

BTW, analogRead() performs multipe consecutive reads and returns an average over these readings while DMA reading only takes one (IIRC) and hence is much faster for that reason too.

2 Likes

I believe the reason why the code in the original post doesn’t work is the buffer is very small (100 samples), and the ADC DMA is not stopped.

At 16000 samples/sec, both buffers will be filled 160 times per second, or every 6.25 milliseconds. So after 6.25 milliseconds, the code jumps to the serial output phase.

But since the samples are written to Serial1 at 9600 baud, at around 5 bytes per sample, only around 200 samples/second can be printed so it will take at least 500 ms to print out the 100 samples.

But since the ADC DMA is not stopped, the values are continuously being updated while they are being printed, every 6.25 ms., which is why the values look random.

Copying to a second large buffer, as I did in my example, is what you probably want to do. That will also get rid of the second set of interleaved samples from the second ADC by averaging, cutting the data size in half.

4 Likes

Hi @aarshkd any update from you side will be appreciate after @rickkas7 post.
I’m curious if you were able to manage this issue :see_no_evil:

@dreamER I was stuck with another project with me. I am going to try it this week. I am going to test what @rickkas7 suggested. I promise I will post back my results.

1 Like

Ok. I am back with some results. As @rickkas7 mentioned earlier, The main issue in my code was printing data while ADC and DMA are still running.

With the @rickkas7’s code, I got answer something like this while putting 8KHz square as an input. This is what I was trying to implement.

4095
56
4095
73
4095
52
4095
49
4095
52
4095
55
4095
61
4095
47
4095
22
4095
45
4095
26

I also would like to mention that @peekay123 said correctly that sampling 8KHz signal at 16KHz sampling rate is not going to give me the cleanest result as it’s the minimum Nyquist sampling rate. I did confirm with my requirement and practically signal that I have to sample is of maximum 4KHz frequency.

And while checking out the ADC data, I deleted the averaging two consecutive samples just to see what does it look like. It turned out that DMA stores data that looks something like this while feeding 8KHz signal while sampling at 16KHz rate.

4095
4095
56
56
4095
4095
70
73
4095
4095
52
50
4095
4095

and so on...

This doesn’t mean that I don’t have the solution of my OP. I am just curios to what is happening in the ADC.

  1. Is this happening due to Dual Regular Simultaneous Mode of the ADC?
  2. If yes, in what sequence does conversion and data storing happen? like, ADC1 and ADC2 samples at the same time → DMA stores the ADC1 data to the mentioned location and then increases the memory location (DMA_MemoryInc_Enable) → DMA stores the ADC2 data to the next memory location and then again increases the location and so on…

And thank you so much everyone for helping me out with this. I hope this will help someone in the future.

  1. Is this happening due to Dual Regular Simultaneous Mode of the ADC?

Yes.

ADC1 and ADC2 samples at the same time → DMA stores the ADC1 data to the mentioned location and then increases the memory location (DMA_MemoryInc_Enable) → DMA stores the ADC2 data to the next memory location and then again increases the location and so on

Yes, that is how it works.

1 Like