SAM L10/L11 Analog-to-Digital Converter (ADC)

Last modified by Microchip on 2023/11/21 22:10

Overview

The Analog-to-Digital Converter (ADC) converts analog signals to digital values. It has up to 12-bit resolution and is capable of a sampling rate of up to 1 MSPS. The input selection is flexible, and both differential and single-ended measurements can be performed. In addition, several internal signal inputs are available. The ADC can provide both signed and unsigned results.

ADC measurements can be started by either application software or an incoming event from another peripheral in the device. ADC measurements can be started with predictable timing and without software intervention. Both internal and external reference voltages can be used.

An integrated temperature sensor is available for use with the ADC. The Voltage Reference output (INTREF) reference, as well as the scaled I/O and core voltages, can also be measured by the ADC. The ADC has a compare function for accurate monitoring of user-defined thresholds, with minimum software intervention required.

The ADC can be configured for 8-, 10- or 12-bit results. ADC conversion results are provided left- or right-adjusted, which eases calculation when the result is represented as a signed value.

A Direct Memory Access (DMA) channel is available to move the ADC results directly to memory or peripherals when conversions are done.

Features

  • 8-bit, 10-bit, or 12-bit resolution
  • Up to 1,000,000 samples per second (1 MSPS)
  • Differential and single-ended inputs
    • Up to 10 analog inputs: 10 positive and eight negative, including internal and external
  • Internal inputs:
    • Internal temperature sensor
    • Bandgap voltage, INTREF voltage reference
    • Scaled core supply
    • Scaled I/O supply
    • Digital-to-Analog Converter (DAC)
    • Operational Amplifier Controller (OPAMP)
  • Single, continuous, and sequencing options
  • Windowing monitor with selectable channel
  • Conversion range: Vref = [1.0V to VDDANA ]
  • Built-in internal reference and external reference options
  • Event-triggered conversion for accurate timing (one event input)
  • Optional DMA transfer of conversion settings or result
  • Hardware gain and offset compensation
  • Averaging and oversampling with decimation to support up to 16-bit result
  • Selectable sampling time
  • Flexible power or throughput rate management

Back to top

Block Diagram

SAML10 ADC block diagram

Back to top

Principle of Operation

Using the ADC's I/O lines requires the I/O pins to be configured using the IO Pin Controller (PORT).

I/O-pins (AINx), as well as the VREFA/VREFB reference voltage pins, are analog inputs to the ADC. Any internal reference source, such as a bandgap voltage reference, or DAC, must be configured and enabled prior to its use with the ADC. The analog signals of the Analog Comparator (AC), ADC, DAC, and OPAMP can be interconnected. The AC and ADC peripheral can request the OPAMP using an analog ONDEMAND functionality.

The ADC will continue to operate in any Sleep mode where the selected source clock is running. The ADC’s interrupts, except the OVERRUN interrupt, can be used to wake up the device from Sleep modes. Events connected to the event system can trigger other operations in the system without exiting Sleep modes.

The ADC bus clock (CLK_ADC_APB) can be enabled in the Main Clock, which also defines the default state. The ADC requires a generic clock (GCLK_ADC). This clock must be configured and enabled in the Generic Clock Controller (GCLK) before using the ADC. A generic clock is asynchronous to the bus clock. Due to this asynchronicity, writes to certain registers will require synchronization between the clock domains. Refer to the "Synchronization" section in the device data sheet ("Oscillators Controller" chapter) for further details.

The DMA request line is connected to the DMA Controller (DMAC). Using the ADC DMA requests requires the DMA Controller to be configured first. The interrupt request line is connected to the interrupt controller. Using the ADC interrupt requires the interrupt controller to be configured first. The events are connected to the Event System.

By default, the ADC provides results with 12-bit resolution. 8-bit or 10-bit results can be selected in order to reduce the conversion time. The ADC has an oversampling with a decimation option that can extend the resolution to 16 bits. The input values can be either internal (e.g., an internal temperature sensor) or external (connected I/O pins). The user can also configure whether the conversion should be single-ended or differential.

Refer to the "ADC – Analog-to-Digital Converter" chapter in the "Ultra Low-Power, 32-bit Cortex-M23 MCUs with TrustZone, Crypto, and Enhanced PTC" SAM L10/L11 family datasheet for more details.

Back to top

Code Example 1

/***
 *** The example has no copyright and can be used by anyone.
 *** The following example is based on Device File Package
 *** required to compile the macro definitions used.
 *** The Device File Package is available by downloading Atmel Studio 7.
 ***/


/***
 *** In this example,  The ADC is triggered by the RTC every 1s to start a conversion.
 *** An interrupt is then generated once the ADC result is ready.
 *** ADC is configured as Event USER using the Event system,
 *** RTC is configured to be the Event GENERATOR in the Event System
 ***/


/***
 *** Function to configure the RTC in calendar mode
 *** The RTC is also confgured to provide an Event every second.
 *** This Even twill be then used to trigger the ADC Start conversion
 ***/

void RTC_Configure(void)
{
/***
  *** Enable XOSC32K (for RTC)
  ***/

 OSC32KCTRL->XOSC32K.reg = OSC32KCTRL_XOSC32K_ENABLE|
                           OSC32KCTRL_XOSC32K_XTALEN|
                           OSC32KCTRL_XOSC32K_EN1K|
                           OSC32KCTRL_XOSC32K_RUNSTDBY|
                           OSC32KCTRL_XOSC32K_STARTUP(5);
while(OSC32KCTRL->STATUS.bit.XOSC32KRDY==0);

/*** Enable RTC Clock
  *** at the peripheral clock mask
  *** (This clock is enabled by default
  ***/

 MCLK->APBAMASK.reg |= MCLK_APBAMASK_RTC;

/***
  *** Select source clock for RTC : Xosc32k
  ***/

 OSC32KCTRL->RTCCTRL.bit.RTCSEL = OSC32KCTRL_RTCCTRL_RTCSEL(OSC32KCTRL_RTCCTRL_RTCSEL_XOSC1K_Val);

/*** Disable RTC ((Enable Protected) ***/
 RTC->MODE2.CTRLA.reg &= ~(RTC_MODE2_CTRLA_ENABLE);
/*** ( write synchronized) ***/
while(RTC->MODE2.SYNCBUSY.reg & RTC_MODE2_SYNCBUSY_ENABLE);

/*** Reset RTC ***/
 RTC->MODE2.CTRLA.reg = (RTC_MODE2_CTRLA_SWRST);
/*** ( write synchronized) ***/
while(RTC->MODE2.SYNCBUSY.reg & RTC_MODE2_SYNCBUSY_SWRST);

/*** Configure RTC in mode 2 (calendar) ***/
 RTC->MODE2.CTRLA.reg = RTC_MODE2_CTRLA_PRESCALER(RTC_MODE2_CTRLA_PRESCALER_DIV1024_Val)|
                                                  RTC_MODE2_CTRLA_MODE(RTC_MODE2_CTRLA_MODE_CLOCK_Val);

/***
  *** Clock sync enabled:
  *** means that a synchronization will be required
  *** to read the CLOCK value register.
  *** This will prevent reading bad values.
  ***/

 RTC->MODE2.CTRLA.bit.CLOCKSYNC = 1;
while(RTC->MODE2.SYNCBUSY.bit.CLOCKSYNC);

/***
  *** Configure RTC Periodic Event Generation
  *** one event every 1024/1024 seconds to trigger the ADC.
  ***/

  RTC->MODE2.EVCTRL.reg = RTC_MODE2_EVCTRL_PEREO7;

      /*** !!! ADD these lines if RTC interrupt is required !!! ***/
      /*** Clear All interrupt flags
       RTC->MODE2.INTFLAG.reg = 0xFFFF;

       /*** set interrupt every seconds ***/

       RTC->MODE2.INTENSET.reg |= RTC_MODE2_INTENSET_PER7;

      /*** Enable RTC interrupt at core Level (NVIC) set the highest priority ***/
       NVIC_EnableIRQ(RTC_IRQn);
       NVIC_SetPriority(RTC_IRQn,0);

/*** Enable RTC ***/
 RTC->MODE2.CTRLA.bit.ENABLE = 1;
while(RTC->MODE2.SYNCBUSY.bit.ENABLE);
}

/***
 *** Function to configure the ADC as Event user
 *** allowing the RTC trigger to be propagated to the ADC.
 *** At the end of each adc acquisition an interrupt occurs.
 ***/

void Adc_Init_1Hz(void)
{
/***
  *** Configure ADC Start of conversion
  *** as user and RTC as a trigger generator
  *** ADC Start user is linked to the Channel 0.
  *** NO detection because of the asynchronous path
  *** EVSYS Channel 0 can run in STBY
  *** Channel 0 Asynchronous path
  *** Channel 0 RTC PEREO7 (fperiodic(n)=(fCLK_RT_OSC / 2^(7+3)) =1Hz)
  ***/

 EVSYS->USER[EVSYS_ID_USER_ADC_START].reg=        EVSYS_USER_CHANNEL(EVSYS_USER_CHANNEL_0);
 EVSYS->Channel[0].CHANNEL.reg = ( EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT|    
                                   EVSYS_CHANNEL_RUNSTDBY|    
                                   EVSYS_CHANNEL_ONDEMAND|         
                                   EVSYS_CHANNEL_PATH_ASYNCHRONOUS|    
                                   EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_RTC_PEREO7));        

/*** Enable ADC result ready interrupt ***/
 ADC->INTENSET.bit.RESRDY = 0x01;

/*** Enable resready ADC interrupt (ID 22) ***/
 NVIC_EnableIRQ(ADC_RESRDY_IRQn);
 NVIC_SetPriority(ADC_RESRDY_IRQn,1);

/*** Enable the ADC ***/
 ADC->CTRLA.bit.ENABLE    =    0x1;
/*** ( write synchronized) ***/
while(ADC->SYNCBUSY.reg & ADC_SYNCBUSY_ENABLE);
}

/*** ADC Interrupt Handler ***/
void ADC_RESRDY_Handler(void)
{
uint16_t adc_result;

/*** Read and clear Interrupt status register ***/
 ADC->INTFLAG.reg;
 adc_result = ADC->RESULT.reg;   
}

Back to top

Code Example 2

/***
 *** The example has no copyright and can be used by anyone.
 *** The following example is based on Device File Package
 *** required to compile the macro definitions used.
 *** The Device File Package is available by downloading Atmel Studio 7.
 ***/


/***
 *** In this example,  The ADC is triggered by the TC0 every 0.001s (1kHz) to start a conversion.
 *** An interrupt is then generated once the ADC has fill a ADC_DATA_CAPTURE_SIZE samples buffer using the DMA.
 *** ADC is configured as Event USER using the Event system,
 *** TC0 is configured to be the Event GENERATOR in the Event System
 ***/


#define ADC_DATA_CAPTURE_SIZE 64
#define ADC_BUFFER_SIZE ADC_DATA_CAPTURE_SIZE

/*** ADC data buffer ***/
extern uint16_t adc_data[ADC_BUFFER_SIZE];

/*** Create DMA descriptor ***/
extern volatile __attribute__((__aligned__(128))) DmacDescriptor descriptor_ADC;

/*** Initial write back memory section. ***/
extern volatile __attribute__((__aligned__(128))) DmacDescriptor write_back_section_ADC;

/***
 *** Function to configure  DMA Descriptor  and
 *** initialize DMA Controler and channels
 ***/

void DMA_init(void)
{

/***
  *** Descriptor configuration:
  *** - Validate the Descriptor  
  *** - No Event out generated by the DMA
  *** - Channel will be disabled if it is the last
  ***   block transfer in the transaction and block interrupt
  *** - 16-bit bus transfer
  *** - The source address is always the same
  *** - The destination address is incremented
  *** - Step size settings apply to the destination address
  *** - Next ADDR = ADDR + (BEATSIZE+1) * 1
  ***  - DMA_BITCOUNT_VALUE 16-bit transfer before waking up the core
  ***/

 descriptor_ADC.BTCTRL.bit.VALID = 1;
 descriptor_ADC.BTCTRL.bit.EVOSEL = DMAC_BTCTRL_EVOSEL_DISABLE_Val;
 descriptor_ADC.BTCTRL.bit.BLOCKACT = DMAC_BTCTRL_BLOCKACT_INT_Val;
 descriptor_ADC.BTCTRL.bit.BEATSIZE = DMAC_BTCTRL_BEATSIZE_HWORD_Val;
 descriptor_ADC.BTCTRL.bit.SRCINC = 0;
 descriptor_ADC.BTCTRL.bit.DSTINC = 1;    
 descriptor_ADC.BTCTRL.bit.STEPSEL = DMAC_BTCTRL_STEPSEL_DST_Val;    
 descriptor_ADC.BTCTRL.bit.STEPSIZE = DMAC_BTCTRL_STEPSIZE_X1_Val;
 descriptor_ADC.BTCNT.reg = ADC_BUFFER_SIZE;        

/***
  *** Set transfer size,
  *** source address and
  *** destination address:
  *** - source address is the ADC result register
  *** - destination address in the RAM
  ***
  ***/

 descriptor_ADC.SRCADDR.reg = (uint32_t)(&(ADC->RESULT));    
 descriptor_ADC.DSTADDR.reg = (uint32_t)(&adc_data[0]+(ADC_BUFFER_SIZE *(DMAC_BTCTRL_STEPSIZE_X1_Val+1)));

/*** Set next transfer descriptor address ***/
 descriptor_ADC.DESCADDR.reg = (uint32_t)&descriptor_ADC;

/***
  *** Setup descriptor base address
  *** and write back section base address
  ***/

 DMAC->BASEADDR.reg = (uint32_t)&descriptor_ADC;
 DMAC->WRBADDR.reg = (uint32_t)&write_back_section_ADC;

/*** Enable all priority level at the same time ***/
 DMAC->CTRL.reg &= ~(DMAC_CTRL_DMAENABLE) ;
 DMAC->CTRL.reg |= DMAC_CTRL_LVLEN(0xf);

/***
  *** Configure the DMAC channel (channel 0)
  *** Disable the channel before configuring it
  *** Perform a soft reset on the channel
  *** Channel must be able to run in STDBY
  *** - Priority for the channel (only one channel used)
  *** - DMA Trigger source is ADC result ready
  *** - Trig every BEAT
  *** - No event system action
  *** - No software command
  ***/

 DMAC->CHID.reg = DMAC_CHID_ID(0x00);
 DMAC->CHCTRLA.reg &= ~DMAC_CHCTRLA_ENABLE;
 DMAC->CHCTRLA.reg = DMAC_CHCTRLA_SWRST;    
 DMAC->CHCTRLA.reg = DMAC_CHCTRLA_RUNSTDBY;            
 DMAC->CHCTRLB.reg = ( DMAC_CHCTRLB_LVL(0x0)|    
                       DMAC_CHCTRLB_TRIGSRC(ADC_DMAC_ID_RESRDY)|
                       DMAC_CHCTRLB_TRIGACT_BEAT    |
                       DMAC_CHCTRLB_EVACT_NOACT    |    
                       DMAC_CHCTRLB_CMD_NOACT        );

/*** Enable DMA interrupts ***/
 DMAC->CHID.reg = DMAC_CHID_ID(0x00);
 DMAC->CHINTENSET.reg =    DMAC_CHINTENSET_TCMPL;
 NVIC_EnableIRQ(DMAC_0_IRQn);

/*** Start the DMA ***/
/*** Enable DMA Channel 0 ***/
 DMAC->CHID.reg = DMAC_CHID_ID(0x00);
 DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE;
while(DMAC->CHSTATUS.bit.BUSY);
/*** Start the DMA Controller ***/
 DMAC->CTRL.reg |= (DMAC_CTRL_DMAENABLE);
}

/***
 *** TC0 is used to trigger ADC Start conversionusing the Event System,
 *** at 1KHZ frequency based on XOSC32K.
 ***/

void init_TC0_1KHz(void)
{
/***
  *** Enable XOSC32K (for RTC)
  ***/

 OSC32KCTRL->XOSC32K.reg = OSC32KCTRL_XOSC32K_ENABLE|
                           OSC32KCTRL_XOSC32K_XTALEN|
                           OSC32KCTRL_XOSC32K_EN32K|
                           OSC32KCTRL_XOSC32K_RUNSTDBY|
                           OSC32KCTRL_XOSC32K_STARTUP(5);

while(OSC32KCTRL->STATUS.bit.XOSC32KRDY==0);

/***
  *** Enable GCLK2 with XOSC32K as clock source
  *** for TC0 for ADC 1KHz Triggering
  ***/

 GCLK->GENCTRL[2].reg = GCLK_GENCTRL_DIV(1) | CURRENT_32KOSC |GCLK_GENCTRL_GENEN;
/*** (write synchronized) ***/
while((GCLK->SYNCBUSY.reg & GCLK_SYNCBUSY_GENCTRL2));    
 GCLK->PCHCTRL[GCLK_TC0].reg = (GCLK_PCHCTRL_CHEN|GCLK_PCHCTRL_GEN_GCLK2);

/*** Disable TC0 (Enable Protected) ***/
 TC0->COUNT8.CTRLA.bit.ENABLE = 0;
/*** (write synchronized) ***/
while(TC0->COUNT8.SYNCBUSY.reg & TC_SYNCBUSY_ENABLE);

/***
  *** TC0 in 32-bit counter with GLCK for
  *** synchronization and a DIV16 to get
  *** 1KHz event generation for the ADC.
  ***/

 TC0->COUNT8.CTRLA.reg |= (TC_CTRLA_MODE(TC_CTRLA_MODE_COUNT8_Val) |
                           TC_CTRLA_PRESCALER(TC_CTRLA_PRESCALER_DIV1_Val));

/***
  *** TC0 Wave generator is configured
  *** as Match frequency generator (MFRQ)
  ***/

 TC0->COUNT8.WAVE.reg = (TC_WAVE_WAVEGEN_MFRQ);

/***
  *** TC0 is an event generator
  *** based on compare value
  ***/

 TC0->COUNT8.EVCTRL.reg = (TC_EVCTRL_MCEO0);
/*** 32768/32 gives 1024Hz ***/
 TC0->COUNT8.CC[0].reg    =    32;
/*** (write synchronized) ***/
while(TC0->COUNT8.SYNCBUSY.reg & TC_SYNCBUSY_CC0);

/*** Enable TC0 ***/
 TC0->COUNT8.CTRLA.bit.ENABLE = 1;
/*** (write synchronized) ***/
while(TC0->COUNT8.SYNCBUSY.reg & TC_SYNCBUSY_ENABLE);        
}

/***
 *** DMA Interrupt Handler function is used
 *** for the 1KHz buffered acquisition
 ***/

void DMAC_0_Handler(void)
{
/*** Clearing the interrupt falg and status ***/
 DMAC->CHID.reg = DMAC_CHID_ID(0x00);
 DMAC->CHINTFLAG.reg = DMAC_CHINTFLAG_TCMPL;
 DMAC->INTPEND.reg = DMAC_INTPEND_ID(0x00)|DMAC_INTPEND_TCMPL;

 processAdcBuffer(); // !!! This function is not provided in the example!!!
}

/***
 *** Initialize the ADC and start collecting at 1 kHz buffered acquisition,
 *** meaning, the ADC ISR is called at the end of the buffer fill.
 ***
 *** NOTE: 1KHz may be 1024 Hz or 1000 Hz depending on which is easier, as we
 ***       don't want to penalize an MCU for requiring much higher speed clocks
 ***       to subdivide down to one or the other.
 ***/

void ADC_Init_1kHz_buf(void)
{

/*DMAC Channel Description */
 DMA_init();               

/***
  *** Configure ADC Start of conversion
  *** as user and RTC as a trigger generator:
  *** ADC Start user is linked to the Channel 0.
  *** NO detection because of the asynchronous path
  *** EVSYS Channel 0 can run in STBY (if running in low power)
  *** Channel 0 Asynchronous path
  *** Channel 0 TC0 Match/clear trigger adc)
  ***/

 EVSYS->USER[EVSYS_ID_USER_ADC_START].reg= EVSYS_USER_CHANNEL(EVSYS_USER_CHANNEL_0);
 EVSYS->Channel[0].CHANNEL.reg = ( EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT|
                                   EVSYS_CHANNEL_RUNSTDBY|
                                   EVSYS_CHANNEL_ONDEMAND|
                                   EVSYS_CHANNEL_PATH_ASYNCHRONOUS|    
                                   EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_TC0_MC0));

/*** Enable ADC ***/
 ADC->CTRLA.bit.ENABLE = 0x1;
/*** (write synchronized) ***/
while(ADC->SYNCBUSY.reg & ADC_SYNCBUSY_ENABLE);

/*** Enable TC0 ***/
 TC0->COUNT8.CTRLA.bit.ENABLE = 1;
/*** (write synchronized) ***/
while(TC0->COUNT16.SYNCBUSY.reg & TC_SYNCBUSY_ENABLE);    
}

Back to top