ADC and Timer relationship in DUE

Hello, colleagues.

I am quite new at microcontrollers and I am just starting my journey into this field, so my question might seem weird, but.. :slight_smile:

I have Arduino UNO as well, and I worked with 328P registers, I tried to setup timers, ADC etc. And there you are able to set a relationship between ADC and a specific timer (like choosing triggering mode, when ADC conversion is started when OC event occurs), but I cannot realize, how a relationship is set in DUE between ADC and Timer that you use... I have looked through a lot of examples, codes, but I cannot see the logic of relationship. Is it hidden somewhere deeper? How is it working in DUE? Am I thinking properly at all?

Thanks a lot for you answers.

You seem to be talking about timer triggered ADC. You can do this. It is discussed in the SAM manual in 43.6.5. You can connect basically any of the timers so long as they are in waveform mode. This could all be a bit tricky if you are new to the whole process but look at the DueTimer library (GitHub - ivanseidel/DueTimer: ⏳ Timer Library fully implemented for Arduino DUE) for an example of how to set up timers. You will likely have to provide some different settings but the code will show you how you set things up. You have to do this same type of thing to configure the ADC as well. The Arduino core is not going to help you out with any of this.

Thanks a lot for your answer. I will check that out.

I had a look at some more examples, but I still cannot figure out, why some people are using ADC in free running mode and setting timers as well. What is the purpose of that?

Here is a sample code. IT IS NOT MY CODE. I just found such one.

//timer library
#include <ARMtimer.h>
#undef HID_ENABLED

int result = 0;

int sampleCount = 0;
int sampleFrequency = 100000;
// unsigned long sampleTime = 1000; //in millis
const int numSamples = 2048;
int resultArray[numSamples];
unsigned long programtime = 0;
bool once = false;

//setup adc function

void prepareADC(){ 
	
  pmc_enable_periph_clk(ID_ADC);
  adc_init(ADC, SystemCoreClock, ADC_FREQ_MAX, ADC_STARTUP_FAST);
  adc_enable_channel(ADC, ADC_CHANNEL_7); 
  ADC->ADC_MR |= 0x80; // free running
  adc_enable_interrupt(ADC, ADC_IER_DRDY); 
  NVIC_EnableIRQ(ADC_IRQn);
  adc_start(ADC); 
}


void ADC_Handler(void)  {
	//Check the ADC conversion status
	if ((adc_get_status(ADC) & ADC_ISR_DRDY) == ADC_ISR_DRDY) {
		//Get latest digital data value from ADC and can be used by application
		result = adc_get_latest_value(ADC);
	}
}

volatile void receivedata()
{
	if (sampleCount < numSamples){
	   resultArray[sampleCount] = result;
	}  
	sampleCount = sampleCount + 1;
	if (sampleCount == numSamples){
	  sampleCount = 0; 
	}
}


void sendData(){
  for (int j=0; j<= numSamples; j++){
	  Serial.print(j);
	  Serial.print(": ");
	  Serial.println(resultArray[j]);
  } 
}


void setup() {
   Serial.begin(115200);
   prepareADC();
   programtime = millis();
}

void loop() {
  	delay(2000);
	int now = micros();
	startTimer(TC1, 0, TC3_IRQn, sampleFrequency, receivedata);
	sendData();
	stopTimer(TC3_IRQn);
	int dur = micros() - now;
	Serial.print("dur: ");
	Serial.println((float)(dur/numSamples));
	sampleCount = 0; 
}

Well, that's certainly a creative way to do it. That code does set the ADC to free running mode in what seems to be 1MHz sampling. Every micro second it calls the interrupt handler which updates a variable. A timer is also set up and when it "ticks" it calls an interrupt handler which in turn takes the current value of that variable set by the ADC interrupt and uses it. So, the ADC is not running with the timer but instead both are being used sort of in concern to still basically do the same thing. Why would someone do that? I have no idea. This sketch wastes all sorts of time by constantly updating the ADC readings when there is no need to. It would have been better to directly couple the ADC readings to a timer but maybe the person who wrote that sketch didn't know how or otherwise was more comfortable with their approach. Still, the approach in that sketch will work so you can use it if you want. It'll be fine so long as you don't start to need more performance. The Due is 84MHz so it has power to spare for sketches like this.

Well... I cannot figure out then. I am so badly lost in this... I have been studying registers of SAM for few days and it doesn't look promising. Perhaps somebody could assist me?

What I have now is this (briefly):

#define   SMP_RATE          96000UL 
#define   CLK_MAIN       84000000UL
#define   TMR_CNTR       CLK_MAIN / (2 * SMP_RATE)

const int sampleQty = 2048;
double samples[sampleQty];
int currSample;
volatile uint8_t flag;

void pio_TIOA0 ()  // Configure Ard pin 2 as output from TC0 channel A (copy of trigger event)
{
	PIOB->PIO_PDR = PIO_PB25B_TIOA0 ;  // disable PIO control
	PIOB->PIO_IDR = PIO_PB25B_TIOA0 ;   // disable PIO interrupts
	PIOB->PIO_ABSR |= PIO_PB25B_TIOA0 ;  // switch to B peripheral
}
void tmr_setup ()
{
	pmc_enable_periph_clk(TC_INTERFACE_ID + 0 *3 + 0); // clock the TC0 channel 0
	TcChannel * t = &(TC0->TC_CHANNEL)[0] ;            // pointer to TC0 registers for its channel 0
	t->TC_CCR = TC_CCR_CLKDIS ;                        // disable internal clocking while setup regs
	t->TC_IDR = 0xFFFFFFFF ;                           // disable interrupts
	t->TC_SR ;                                         // read int status reg to clear pending
	t->TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1 |           // use TCLK1 (prescale by 2, = 42MHz)
	   		    TC_CMR_WAVE |                          // waveform mode
				TC_CMR_WAVSEL_UP_RC |                  // count-up PWM using RC as threshold
				TC_CMR_EEVT_XC0 |					   // Set external events from XC0 (this setup TIOB as output)
				TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_CLEAR |
				TC_CMR_BCPB_CLEAR | TC_CMR_BCPC_CLEAR ;
	t->TC_RC = TMR_CNTR;              // counter resets on RC, so sets period in terms of 42MHz clock
	t->TC_RA = TMR_CNTR / 2;           // roughly square wave
	t->TC_CMR = (t->TC_CMR & 0xFFF0FFFF) | TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_SET ;  // set clear and set from RA and RC compares
	t->TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG ;  // re-enable local clocking and switch to hardware trigger source.  
}

void adc_setup () 
{
	pmc_enable_periph_clk(ID_ADC); 
	adc_init(ADC, SystemCoreClock, ADC_FREQ_MAX, ADC_STARTUP_FAST);
	adc_disable_all_channel(ADC);
	adc_enable_interrupt(ADC, ADC_IER_EOC7);  // I use only AN0, so as I understand this will trigger handler after conversion. However, I need to read 5-6 analog channels right away... :(
	NVIC_EnableIRQ(ADC_IRQn);               // enable ADC interrupt vector
	adc_enable_channel(ADC, ADC_CHANNEL_7);  // AN0
	adc_configure_trigger(ADC, ADC_TRIG_TIO_CH_0, 0); 
	adc_start(ADC);
}
void ADC_Handler(void)  {
	if (((adc_get_status(ADC) & ADC_ISR_EOC7) == ADC_ISR_EOC7)) {
		samples[currSample] = adc_get_latest_value(ADC);
		currSample += 1;
	};
}

void setup() {
   Serial.begin(115200);
   adc_setup ();         
   tmr_setup ();         
   pio_TIOA0 ();  // drive Arduino pin 2 at SMPL_RATE to bring clock out	
}

void loop() {
}

I do setup in Setup() event and then Due just stucks, when I upload it... nothing happens. I tried several more modifications to this, but no use.. Then I did a second approach like this (from Atmel site):

void adc_setup(void) {
	adc_init(ADC, SystemCoreClock, ADC_FREQ_MAX, 8);
	adc_configure_timing(ADC, 0, ADC_SETTLING_TIME_3, 1);
	adc_enable_channel(ADC, ADC_CHANNEL_7);
	adc_enable_interrupt(ADC, ADC_IER_DRDY);
	adc_configure_trigger(ADC, ADC_TRIG_SW, 0);
	adc_start(ADC);
}

void ADC_IrqHandler(void) {
	// Check the ADC conversion status
	if ((adc_get_status(ADC) & ADC_ISR_DRDY) == ADC_ISR_DRDY) {
		// Get latest digital data value from ADC and can be used by application
		uint32_t result = adc_get_latest_value(ADC);
	}
}

void setup() {
   Serial.begin(115200);
   adc_setup ();         
 }

void loop()  {
}

And this one does stuck as well... Have no idea, what is wrong with the setup... Basically, my goal is to read 5 analog channels right away. I need at least 100Khz of sampling rate for each channel (to read 40Khz sound saves), so at least 500-600Khz of sampling freq. Moreover, I do not know how to measure actual frequency, because, when you set it up, sometimes (because of various reasons) it is not as fast as defined by parameters, so what I do is I write down start time in micros() and then write down end time. Dividing this time by sample number (quite big) I get approximate duration of one sample. However, when I tried this here, I got soma magic results as well.. any ideas?

Anyway, thanks a lot for any help, because I am really stuck with this for DAYS... if you manage to help me, that would be fabulous.

Thanks a lot.

And this one does stuck as well...

How do you know? I can't see any debug messages, or timestamps, loop is just empty.

Well, I usually put some sort of Serial.print into loop event. Here, I have deleted them to make code a bit shorter. And then, when I upload a code, Tx LED is dead, no messages and so on. Sometimes I find my errors, but in this case, I do not know, what is wrong.

Anyone? Please... I need at least some sort of suggestion so bad...

I use an example from this thread:
http://forum.arduino.cc/index.php?topic=205096.0
and it's working

Well... that is weird. I saw this example, but last time I failed to implement it. This time it worked :slight_smile: It seems that Magician does have a bit of magic.

Anyway, I noticed my issue, perhaps you could explain this:

void ADC_Handler(void) {
	// if or while?
	if ((ADC->ADC_ISR & ADC_ISR_EOC7) && (ADC->ADC_ISR & ADC_ISR_EOC6)) {
		int val = *(ADC->ADC_CDR + 7);
		samples[currSample] = val;
		val = *(ADC->ADC_CDR + 6);
		samples2[currSample] = val;
		currSample += 1;
		if (currSample == sampleQty) {TC_Stop(TC0, 0);} 
	}
}

What is the difference there, where "if" is used and "while"? I mean, when I use "while", programs stops responding and stucks, but with "if" it seems to be working OK. What is the logic behind this facet? ADC interrupt is configured as this:

adc_enable_interrupt(ADC, ADC_IER_EOC7); // enable EOC7 interrupt.

Thanks.