ATSAM3X8E ADC as a comparator

Hi friends this is Sivaram from india :slight_smile: ,

I want to use Due ADC as comparator to generate events for my application. I developed the code to use ADC as comparator and generate the comparison based interrupt for an event occurrence with a triggering frequenncy of 10KHz. The code is attached below, the following code generates the interrupt when the analog signal is in between the range of comparison window at the pin A1 of Due. The due ADC is having the single interrupt handler for all comparisons at different pins of board (To the best of my knowledge).

for instance lets consider the comparison for two signals which are phase shifted by 90 degrees. The comparison value is 400 in ADC_CWR (comparison window register). The hand sketch of the signals and the required event occurrence instants are given in the pdf file attached.

Can I perform the multiplexing of the signals at pins A1 and A2 to be compared with the value in ADC_CWR at run time in ADC registers to generate the interrupt, if possible please help.

Thanks in advance.

void setup() 
{

  pinMode(13,OUTPUT);

  ADC_setup();
  
  PWM_channel_setup(); 
  
}

void loop() 
{
  // Do nothing.
}

void ADC_setup(void)
{
  pmc_enable_periph_clk(ID_ADC);                                                             // power on the ADC(enabling the clock)
  ADC->ADC_CR                   = (ADC_CR_SWRST);                                                       // Software Reset of ADC.
  ADC->ADC_CHDR                 =  0xFFFF;                                                              // disabling the channels.
  ADC->ADC_IDR                  = (0xFFFFFFFF) ;                                                        // disabling the interrupts.
    
  ADC->ADC_MR                  |= (ADC_MR_TRGEN)|
                                  (ADC_MR_TRGSEL_ADC_TRIG4);                                            // enabling the hardware trigger for the ADC, not touhing the tracktim and conversion time of the module. 
  ADC->ADC_CHER                 = (ADC_CHER_CH7);                                                       // Selecting the channel-7
  ADC->ADC_EMR                 |= (ADC_EMR_CMPSEL(7))|                                                  // channel-7 is selected for comparision.
                                  (ADC_EMR_CMPMODE_IN);
  ADC->ADC_CWR                 |= ADC_CWR_HIGHTHRES(2000)|
                                  ADC_CWR_LOWTHRES(1999);
  ADC->ADC_IER                 |= (ADC_IER_COMPE);
  
  NVIC_EnableIRQ(ADC_IRQn);                                                                             // enabling the ADC interrupt. 
  ADC->ADC_CR                   = (ADC_CR_START);                                                       // Starting the ADC module.
  
}

void PWM_channel_setup(void)
{
  pmc_enable_periph_clk(ID_PWM);                                           // power on the ADC
  
  REG_PWM_WPCR    = 0x50574DF0;                                            //
  
  // connecting the channel outputs to the MCU pins.
  REG_PIOC_PDR   |= PIO_ABSR_P2 | PIO_ABSR_P3;
  REG_PIOC_ABSR  |= PIO_ABSR_P2 | PIO_ABSR_P3;
  REG_PIOC_PUDR   = 0x0000FFFF;

  // initializing the channel zero for generating the event trigger for ADC peripheral.
  REG_PWM_CMR0    = PWM_CMR_CPRE_MCK|                                                     // Initializing the channel-0.
                    PWM_CMR_CES|
                    PWM_CMR_DTE|
                    PWM_CMR_DTHI;
  REG_PWM_CPRD0   = PWM_CPRD_CPRD(8404);                                                  // PWM period is 100us approximately.
  REG_PWM_CDTY0   = PWM_CDTY_CDTY(4000);
  REG_PWM_DT0     = 0x00000000;

  REG_PWM_CMPM0   = PWM_CMPM_CEN|                                                         // PWM Comparison 0 Mode Register
                    PWM_CMPM_CPR(0);                                                      // enabling the counter event selection.

  REG_PWM_CMPV0   = PWM_CMPV_CV(7800);                                                    // comparison value for the event happening.
                                                                                          // I the comparision value should be less than the channel-0 period value.

  REG_PWM_ELMR    = PWM_ELMR_CSEL0;                                                       // Event Line 0 comparision selection. 
  REG_PWM_ENA     = 0x00000001;                                                           // Enabling the channel one.
}

void ADC_Handler(void)
{
  if(ADC->ADC_ISR & (ADC_IER_COMPE))
  {
    PIOB->PIO_ODSR   ^= PIO_ODSR_P27;
  }
}

ADC as comparator.pdf (162 KB)

Select and enable ADC ch7(A0) and ADC ch6(A1) , compare with ADC_CWR, select ch6 and ch7 to be compared with the selected window in ADC_EMR:

From datasheet Page 1326:
The comparison can be done on all channels or only on the channel specified in CMPSEL field of ADC_EMR. To compare all channels the CMP_ALL parameter of ADC_EMR should be set.

Since ch6 and ch7 are selected, conversion order will be: ch6/ch7/ch6/ch7/…During each conversion an interrupt will be triggered by COMPE bit.

IMO there are several means to know wich channel is concerned by the interrupt. One of them is to trigger an interrupt at the end of each conversion in addiditon of interruptions triggered by COMPE bit, by setting EOC bits in ADC_IER, OR follow a static counter inside the interrupt handler.

As per your attached drawing, the order of interruptions will be:

COMPE for ch6/EOC ch6/COMPE for ch7/EOC ch7/…

Note that ADC conversion frequency can be set with a PWM Event Line 0 or 1, or the (internal redirection) output of a Timer Counter.

An example code (not tested though):

volatile boolean Flag;
volatile uint8_t chCounter;

void setup() {
  adc_setup();
}


void loop() {
  
  if (Flag == true)
  {
    Flag = false;
    if (chCounter == 0)
    { // Flag was set for ch 7 window interrupt
    }
    else
    { // Flag was set for ch 6 window interrupt
    }
  }
  
}
void adc_setup ()
{
  PMC->PMC_PCER1 |= PMC_PCER1_PID37;                    // ADC power ON
  ADC->ADC_CR = ADC_CR_SWRST;                           // Reset ADC
  ADC->ADC_MR |=  ADC_MR_TRGEN_EN                       // Hardware trigger select
                  | ADC_MR_LOWRES_BITS_12               // 12 bits resolution
                  | ADC_MR_PRESCAL(1)
                  | ADC_MR_SETTLING_AST3
                  | ADC_MR_TRACKTIM(10)
                  | ADC_MR_TRANSFER(2)
                  | ADC_MR_TRGSEL_ADC_TRIG3;            // Trigger by TIOA2

  ADC->ADC_EMR = ADC_EMR_CMPMODE_HIGH                   // Compare with high threshold
                 | ADC_EMR_CMPSEL(6)                    // Compare channel 6 = A1
                 | ADC_EMR_CMPSEL(7);                   // Compare channel 7 = A0
  //  | ADC_EMR_CMPFILTER(10);               // Number of consecutive compare events necessary
  
  // to raise the flag = CMPFILTER+1
  ADC->ADC_CWR = ADC_CWR_HIGHTHRES(4000);               // Compare with a high threshold of conversion

  ADC->ADC_CHER = ADC_CHER_CH6                          // Enable Channel 6= A1
                  | ADC_CHER_CH7;                         // Enable Channel 7 = A0

  ADC->ADC_IDR = ~(0ul);                                // Disable all interrupts
  ADC->ADC_IER = ADC_IER_COMPE;                         // Interrupt on Compare match enable

  NVIC_EnableIRQ(ADC_IRQn);                             // Enable ADC interrupt
}

void ADC_Handler() {

  ADC->ADC_ISR;                            // Read and clear status register
  // It's useless to test the interrupt bit since only COMPE is selected
  // Do some stuff (keep this as short as possible, e.g. set a flag to be proceed in loop())
  
  chCounter = (chCounter + 1) % 2;        // Set chCounter for the NEXT interrupt
  Flag = true;
}
/*************  Timer Counter 0 Channel 2 to generate PWM pulses thru TIOA2  ************/
void tc_setup() {

  PMC->PMC_PCER0 |= PMC_PCER0_PID29;                      // TC2 power ON : Timer Counter 0 channel 2 IS TC2
  TC0->TC_CHANNEL[2].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK3  // MCK/32, clk on rising edge
                              | TC_CMR_WAVE               // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC        // UP mode with automatic trigger on RC Compare
                              | TC_CMR_ACPA_CLEAR          // Clear TIOA2
                              | TC_CMR_ACPC_SET;           // Set TIOA2


  TC0->TC_CHANNEL[2].TC_RC = 875;  //<*********************  Frequency = (Mck/32)/TC_RC  Hz = 3 KHz
  TC0->TC_CHANNEL[2].TC_RA = 400;  //<********************   Any Duty cycle in between 1 and 874

  TC0->TC_CHANNEL[2].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN; // Reset TC2 counter and enable
}

First of all thanks for the reply.... (ard_newbie)

I will try your code template and tell you tell you about the results.

Hi…

I have tried the following code

// In this code example the ADC is intended to use as comparator.

void setup() 
{
  pinMode(13,OUTPUT);
  ADC_setup();
  PWM_CH0_setup();
}

void loop() 
{
  // Do nothing.
}

void ADC_setup (void)
{
  pmc_enable_periph_clk(ID_ADC);                                                                           // Enable the master clock to ADC module
  ADC->ADC_CR                      = (ADC_CR_SWRST);                                                       // Reset the ADC.
  ADC->ADC_IDR                     = (0xFFFFFFFF) ;                                                        // disabling the interrupts.
  ADC->ADC_IER                     = (ADC_IER_COMPE);
  ADC->ADC_MR                     |= (ADC_MR_TRGEN_EN)|
                                     (ADC_MR_TRGSEL_ADC_TRIG4);                                            // ADC Trigger is PWM event-0 line.
  ADC->ADC_EMR                    |= (ADC_EMR_CMPMODE_HIGH)|
                                     (ADC_EMR_CMPSEL(7));
  ADC->ADC_CWR                    |= (ADC_CWR_HIGHTHRES(600))|
                                     (ADC_CWR_LOWTHRES(0));
  ADC->ADC_CHER                    = (ADC_CHER_CH7);                     
  ADC->ADC_CR                      = (ADC_CR_START);                                                       // Starting the ADC module                       
  NVIC_EnableIRQ(ADC_IRQn);                                                                                // Enabling the ADC interrut handler in NVIC.
}

void PWM_CH0_setup (void)
{
  pmc_enable_periph_clk(PWM_INTERFACE_ID);
  
  REG_PWM_WPCR    = 0x50574DF0;
  
  // connecting the channel outputs to the MCU pins.
  REG_PIOC_PDR   |= PIO_ABSR_P2 | PIO_ABSR_P3;
  REG_PIOC_ABSR  |= PIO_ABSR_P2 | PIO_ABSR_P3;
  REG_PIOC_PUDR   = 0x0000FFFF;

  // initializing the channel zero for generating the event trigger for ADC peripheral.
  REG_PWM_CMR0    = PWM_CMR_CPRE_MCK|                                                     // Initializing the channel-0.
                    PWM_CMR_CES|
                    PWM_CMR_DTE|
                    PWM_CMR_DTHI;
  REG_PWM_CPRD0   = PWM_CPRD_CPRD(8404);                                                  // PWM period is 100us approximately.
  REG_PWM_CDTY0   = PWM_CDTY_CDTY(3000);
  REG_PWM_DT0     = 0x00000000;

  REG_PWM_CMPM0   = PWM_CMPM_CEN|                                                         // PWM Comparison 0 Mode Register
                    PWM_CMPM_CPR(0);                                                      // enabling the counter event selection.

  REG_PWM_CMPV0   = PWM_CMPV_CV(7800);                                                    // comparison value for ADC triggering event's.
                                                                                          // The comparision value should be less than the channel-0 period value.

  REG_PWM_ELMR    = PWM_ELMR_CSEL0;                                                       // Event Line 0 comparision selection. (generates the event signal) 
  REG_PWM_ENA     = 0x00000001;                                                           // Enabling channel-1.
}

void ADC_Handler(void)
{
  ADC->ADC_ISR;
  static uint32_t Vs = 0;
  Vs  = *(ADC->ADC_CDR+7) ;
  if(Vs > 1220)
  {
    digitalWrite(13, HIGH);
  }
  else
  {
    digitalWrite(13, LOW);
  }
}

// This code will work fine for the waveforms upto 500 hz..

the waveform is also attached.

Hey ard_newbie I will try your code tooo.

ADC_comparator.JPG