SAMD21 ADC sampling, cannot get EVSYS event working

Hello,

my intention here (still learning SAMD internals) is regularly sampling ADC and the using DMA to copy data

For sampling I'm using a Timer (TC3) and start ADC sampling on TC3_IRQ. That is working, but with higher frequencies not practical. As well I'd like to save CPU for data processing, not employing as a postman between TC3 and ADC.

I found out in theory I could use EVSYS to pass an event from the TC3 timer to start ADC. And .. that is not working, apparently I missed setting some SAMD register and from the datasheet it is not always clear which one :confused:

When trying to pass a SWEVT to EVSYS, nothing happens (no ADC nor EVSYS interrupt as configured), so I'd assume I missed something on EVSYS configuration:


void setupClock() {
  // Arduino code configures following clocks
  // GCLK0 -> 48MHz Digital Frequency Locked Loop
  // GCLK1 -> 32.768kHz external crystal, otherwise 32.768kHz internal oscillator (for CRYSTALLESS operation)
  // GCLK2 -> 32.768kHz ultra low power internal oscillator
  // GCLK3 -> 8MHz internal oscillator

  // This leaves GCLK4 through to GCLK7 free to use as you wish.
  // we will be using GLCK4  8MHz for sake of learning

  // Run GLCK4 on 8M clock
  SYSCTRL->OSC8M.bit.ENABLE = 1;
  SYSCTRL->OSC8M.bit.PRESC = 0x00; // SYSCTRL_OSC8M_PRESC_1; // divide by 1 

  /* Wait for the clock / oscillator to be ready */
  while(!SYSCTRL->PCLKSR.bit.OSC8MRDY);

  /* Setup GCLK1 using the internal 8M oscillator */
  GCLK->GENCTRL.reg =
      GCLK_GENCTRL_ID(4) |
      GCLK_GENCTRL_SRC_OSC8M  |
      // GCLK_GENCTRL_DIVSEL | /* apply prescaler divider */
      GCLK_GENCTRL_IDC | /* Improve the duty cycle. */
      GCLK_GENCTRL_GENEN;

  /* Wait for the write to complete */
  syncGLCK();

  // https://blog.thea.codes/understanding-the-sam-d21-clocks/
  // Enable TC3 on 8MHz
  GCLK->CLKCTRL.reg = 
                      GCLK_CLKCTRL_ID( GCM_TCC2_TC3 ) | // TC3 clock
                      // GCLK_CLKCTRL_ID( GCM_ADC ) | // Generic Clock ADC
                      GCLK_CLKCTRL_GEN_GCLK4     | // Generic Clock Generator 4 is source
                      GCLK_CLKCTRL_CLKEN ; 
  syncGLCK();

  // Enable ADC
  GCLK->CLKCTRL.reg = 
                      // GCLK_CLKCTRL_ID( GCM_TCC2_TC3 ) | // TC3 clock
                      GCLK_CLKCTRL_ID( GCM_ADC ) | // Generic Clock ADC
                      GCLK_CLKCTRL_GEN_GCLK4     | // Generic Clock Generator 4 is source
                      GCLK_CLKCTRL_CLKEN ; 
  syncGLCK();

  // Enable EVSYS chn 0 CLK4 (the same as the TC3 gen source allowing sync events)
    GCLK->CLKCTRL.reg = 
                      // GCLK_CLKCTRL_ID( EVSYS_GCLK_ID_0) | // Generic Clock for EVSYS
                      GCLK_CLKCTRL_ID(GCM_EVSYS_CHANNEL_0) |
                      GCLK_CLKCTRL_GEN_GCLK4     | // Generic Clock Generator 1 is source
                      GCLK_CLKCTRL_CLKEN ; 
  syncGLCK();

}

/* ------------------------------ */

void setupAdc() {
  PM->APBCMASK.reg |= PM_APBCMASK_ADC;

  ADC->CTRLA.bit.ENABLE = false;
  syncADC();

  ADC->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV16 |    // Divide Clock by 512.
                  #ifdef TEST_ADC_FREERUN
                   ADC_CTRLB_FREERUN |
                  #endif
                   ADC_CTRLB_RESSEL_12BIT;         // 10 bits resolution as default

  // sampling_time = (SAMPLE_LEN+1) * CLK(ADC) / 2
  ADC->SAMPCTRL.bit.SAMPLEN = 0x015;                        // Set max Sampling Time Length
  syncADC();

  ADC->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND;   // No Negative input (Internal Ground)

  // Averaging (see datasheet table in AVGCTRL register description)
  ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_2 |    // 2 sample only (no oversampling nor averaging)
                     ADC_AVGCTRL_ADJRES(0x02ul);   // Adjusting result according to the datasheet

  syncADC();
  
  // this should starting ADC sampling from EVSYS
  // A new conversion will be triggered on any incoming event.
  ADC->EVCTRL.bit.STARTEI = 1;
  syncADC();

  // setup IRQ
  NVIC_DisableIRQ(ADC_IRQn);
  NVIC_ClearPendingIRQ(ADC_IRQn);
  ADC->INTENSET.bit.RESRDY = 1;
  NVIC_SetPriority(ADC_IRQn, 1);
  NVIC_EnableIRQ(ADC_IRQn);
  
  /* Enable the ADC. */
  ADC->CTRLA.bit.ENABLE = true;
  syncADC();

  // Set pin to input mode
  PORT->Group[g_APinDescription[AUDIO_PIN].ulPort].PINCFG[g_APinDescription[AUDIO_PIN].ulPin].reg=(uint8_t)(PORT_PINCFG_INEN) ;
  PORT->Group[g_APinDescription[AUDIO_PIN].ulPort].DIRCLR.reg = (uint32_t)(1<<g_APinDescription[AUDIO_PIN].ulPin) ;
}

/* --------------------- */

void setupTimer() {
  // clock w/ prescaler on 8MHz 
  TC3->COUNT16.CTRLA.bit.ENABLE = 0;
  syncTC3();

  // set 16-bit mode and set waveform 'match frequency'
  TC3->COUNT16.CTRLA.reg |= TC_CTRLA_MODE_COUNT16| TC_CTRLA_WAVEGEN_MFRQ;
  syncTC3();

  // let's keep 8MHz clock 
  // clock_freq / freq / divisor 
  // TC3 FREQ = 1 MHz ( 8MHz / 8) 
  TC3->COUNT16.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV8;
  syncTC3();

  // set compare register value
  // period = 1000000 / freq = 125 us
  // ticks_persiod = ;
  TC3->COUNT16.COUNT.reg = 0;
  // TC3->COUNT16.CC[0].reg = 125;
  // 1kHz 
  TC3->COUNT16.CC[0].reg = 1000; 
  // count down
  TC3->COUNT16.CTRLBCLR.bit.DIR = 1;
  syncTC3();

  // enable IRQ on TC3 MC0
  // NVIC_DisableIRQ(TC3_IRQn);
  // NVIC_ClearPendingIRQ(TC3_IRQn);
  // NVIC_SetPriority(TC3_IRQn, 2);
  // NVIC_EnableIRQ(TC3_IRQn);
  // TC3->COUNT16.INTENSET.bit.MC0 = 1;
  // syncTC3(); 

  // enable TC3
  TC3->COUNT16.CTRLA.bit.ENABLE = 1;

/*
The TC can generate the following output events:
• Overflow/Underflow (OVF)
• Match or Capture (MC)
Writing a '1' to an Event Output bit in the Event Control register (EVCTRL.MCEOx) enables the corresponding output
event. The output event is disabled by writing EVCTRL.MCEOx=0. 
*/
  TC3->COUNT16.EVCTRL.bit.MCEO0 = 1; // enable match event on EVT Chn 0
  // EVACT seems to be for receiving events, not generating
  // TC3->COUNT16.EVCTRL.bit.EVACT = TC_EVCTRL_EVACT(TC_EVCTRL_EVACT_RETRIGGER_Val); // restart TC on event
  syncTC3();

}


/* -------------------------- */

void setupEVSYS() {
  // EVSYS->CTRL.bit.SWRST = 1;

	EVSYS->USER.reg = EVSYS_USER_CHANNEL(0+1) |      // Attach the event user (receiver) to channel 0 (n + 1)
                    // EVSYS_USER_USER(EVSYS_ID_USER_ADC_START);  // Set the event user (receiver) as timer TC3
                    EVSYS_USER_USER(EVSYS_ID_USER_ADC_START);
  // while(EVSYS->CHSTATUS.bit.CHBUSY0!=0);

	EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT |  // No event edge detection
                       // When the asynchronous path is selected, the channel cannot generate any interrupts, 
                       // and the Channel Status register (CHSTATUS) is always zero. No edge detection is available; 
	                     EVSYS_CHANNEL_PATH_RESYNCHRONIZED |
                       EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_TC3_MCX_0) |    // Set event generator (sender) as external interrupt 6
                       EVSYS_CHANNEL_CHANNEL(0);             // Attach the generator (sender) to channel 0
  EVSYS->CTRL.bit.GCLKREQ = 1;
  
  while(EVSYS->CHSTATUS.bit.CHBUSY0!=0);

  // enable IRQ on EVSYS Chnl 0
  EVSYS->INTENSET.bit.EVD0 = 1;
  while(EVSYS->CHSTATUS.bit.CHBUSY0!=0);

  // The EVSYS has the following interrupt sources:
  //   Overrun Channel n (OVRn): for details, refer to The Overrun Channel n Interrupt section.
  //  we're using IRQ only to set/increase some flags so we see events are detected
  NVIC_DisableIRQ(EVSYS_IRQn);
  NVIC_ClearPendingIRQ(EVSYS_IRQn);
  NVIC_SetPriority(EVSYS_IRQn, 2);
  NVIC_EnableIRQ(EVSYS_IRQn);

}


full compilable (not working) code here: TC3-EVSYS-ADC test (not working) - Pastebin.com

Hi @gusto2

The event system has to be switched on in the Power Management (PM) module:

PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;    // Switch on the event system peripheral

Also, the TC timers can only operate with the event system using an asynchronous path, this is mentioned in the SAMD21 errata:

This means that the event system channel doesn't need to be driven with a generic clock.

1 Like