SAM C21 Sigma-Delta ADC Configuration

Last modified by Microchip on 2023/11/10 11:08

This page provides a more detailed description of how to configure the SAM C21 Sigma-Delta ADC peripheral. After reviewing the basic module functionality, we describe the key configuration steps (and associated registers) that need to be configured to implement a simple application where the SAM C21 digitizes a light sensor signal.

Module Overview

Description

The Sigma-Delta Analog-to-Digital Converter (SDADC) converts an analog voltage to a signed 16-bit value by integrating and decimating the output of a sigma-delta modulator. The filtering and decimation are done using a SINC-based filter that has zeroes placed to minimize the aliasing effects of the decimation. This is a third order SINC filter (see the figure below) with an adjustable Oversampling Ratio (OSR) to achieve higher throughput versus signal-to-noise-ratio (SNR). For example, an OSR of 64 provides a an output data rate of 23 ksps while an OSR of 1024 produces an output data rate of 1.4 ksps.

Third order SINC filter Diagram

Why is the decimation filter input 2 bits wide? The SAM C21 Sigma-Delta ADC module is differential, so the output may be positive or negative. Because of this, the bitstream out of the Sigma-Delta modulator is actually 2 bits per sample, because a sign bit is needed.

The SDADC provides a signed result in a 24-bit register to allow for gain and offset correction without overflow in hardware. Because the result is in 2’s complement format, the SDADC result is signed 16-bit (maximum) using ±VREF. Using the internal VREF set at 1.024 V, the SDADC will produce codes to 1.024 V. The internal VREF can be configured to supply a reference of 1.024 V, 2.048 V, and 4.096 V.

The range selected in the REFSEL register must match the supplied VREF and must be set independently of the internal VREF.

Register Interface

Register Interface Diagram

Functional Description

The Sigma Delta Analog-to-Digital Converter (SDADC) can be used for high-resolution DC measurements. These measurements can include temperature sensors, thermocouples, cold-junction compensation, 3-4 Wire RTD sensors, 4-20 mA current loops, current (Shunt), as well as scales/load cells. The Sigma-Delta architecture provides a low-cost solution for these precision measurements, requiring only a simple R-C low pass filter for anti-aliasing.

A generic clock (GCLK_SDADC) is used to generate the CLK_SDADC via a 7-bit prescaler. The GCLK must be configured and enabled before the SDADC can be used. The sampling clock is derived from the CLK_SDADC/4. Therefore, the maximum CLK_SDADC is 6 MHz and the maximum sampling frequency is 1.5 MHz. The GCLK_SDADC is asynchronous to the APB bus clock and, therefore, writes to registers require synchronization. This flexible clocking system allows configuring CLK_SDADC to run in any Sleep Mode. The 7-bit prescaler enables flexible sampling frequency adjustment.

The SDADC supports differential measurements on three analog input channels. The measurements are done using one of four references; internal bandgap, an external voltage on AREFB, DAC output, or AVCC. The voltage reference has a selectable buffer to offers higher input impedance to the external reference.

The SDADC filters and decimates the sigma-delta output bit stream at 16-bit (signed) with programmable rates of CLK_SDADC_FS (prescaled ADADC clock frequency) divided by 64 to 1024. The Output rate is set by modifying the programmable Over Sampling Ratio (OSR). The result is a 2’s complement 24-bit result with programmable gain and offset correction.

The SDADC peripheral supports three interrupts. The result ready flag (INTFLAG.RESRDY) can trigger a DMA transfer or event. The window monitor can generate an event by setting the EVCTRL.WINMONEO] bit. The INTFLAG.OVERRUN flag is set when the previous result is not read before a new result is ready.

Automatic sequences can be configured to enable multiple samples from a single start of conversion request. The order of this conversion is from the lower positive input pair to the upper positive input pair (AINN0, AINP0, AINN1, AINP1 …).

If SEQCTRL register has no bits set to '1', the conversion is done with the selected INPUTCTRL input (MUXSEL). The window monitor can be used to define a threshold and trigger the WINMON flag (or interrupt).

Basic Operation

Initialization

The SDADC must be configured with the peripheral disabled. The sequence to configure and enable the SDADC is:

  • Enable the Generic Clock
    • GCLK_SDADC
  • Select the Voltage Reference and Range
    • REFCTRL.REFRANGE[1:0]
    • REFCTRL.REFSEL[1:0]
  • Set the Conversion Rate and Resolution
    • GCLK_SDADC
    • CTRLB.PRESCALER[7:0] (SDADC_CLK)
    • CTRLB.OSR[2:0] (Over Sampling Ratio)
    • Select trigger source or interrupts
      • SWTRIG.START (Software Trigger)
      • Free Run Mode
      • DMA/Event
      • Timers
    • Sequence Control
      • Automatic Sequences (SEQCTRL)
      • Lower to Upper Positive Pairs
      • SEQSTATUS.SEQBUSY bit will be set when a conversion is initiated and cleared when the sequence is complete
      • The input number is stored with the RESULT
    • Window Monitor Control
      • RESULT compare to threshold
      • WINUT/WINLT (Upper and Lower Thresholds)

Reading the Results

The SDADC result ready flag (INTFLAG.RESRDY) is set when a conversion is complete. When the peripheral is first initialized, the results are only valid after the third conversion. To automate this limitation in hardware, the register set includes the skip register (CTRLB.SKPCNT[3:0]) to automatically skip the first n results.

When in free running mode, the application must read the result before the next result is ready. If this does not happen, the overrun flag (INTFLAG.OVERRUN) will be set.

The result is read from RESULT and is 24-bits wide. To get the 12- to 16-bit conversion result based on the OSR, the application needs to shift the results. However, this can be accomplished in hardware using the SHFTCORR register to define a number of right shifts to be done automatically. Also, a fixed gain can be applied in the same way. Care must be taken to set the gain to at least 1 if not used as it is automatically applied.

Configuration Example

This section explores a simple and basic SDADC configuration, where the SAM C21 SDADC is connected to a light sensor circuit:

SDADC configuration: SAM C21 SDADC connected to a light sensor circuit

The PWM circuit is not required in this application; it is merely an artifact of the development boards used in this example. We will drive pin PB13 low to provide a ground reference to the ADCdelete input.

To see the full project details, visit the SAM C21 Sigma-Delta ADC Example Project page.

Summary of Steps

  1. Initialization
    1. Enable Generic and Synchronous (APBx) Clocks
    2. Configure I/O Pins & PMUX
    3. Set Sampling Frequency (CLK_SDADC)
    4. Set VREF
    5. Set Correction Values
    6. Set Input Channel
    7. Enable Module
  2. Reading Results
    1. Start Conversion(s)
    2. Wait for the RESRDY Flag
    3. Read RESULT and shift right in the application or in the hardware via SHIFTCORR
Initialization
Configure Clocks

The following diagram summarizes the clocking configuration for this application:

Clocking configuration Diagram

The default oscillator source clock is the internal OSC48M 48 MHz oscillator, which is divided by 12 to provide a 4 MHz clock source on reset.

This clock is fed to the GCLK generator module, and is used to feed two GCLK generators:

  • GCLK_0 is used to supply the CPU and synchronous (APBx) bus clocks (via the MCLK unit), and
  • GCLK_1 is used to source the asynchronous clocks for the SDADC peripheral channel (Channel 35).

As indicated in the above diagram, the SDADC module requires two clocks. The first is the APB clock (peripheral bus), which comes from the MCLK module. The bit that enables the SDADC clock is in the APBCMASK register:

APBCMASK Register

The following code sample performs this assignment:

// Turn On SDADC APB Clock
MCLK->APBCMASK.reg = MCLK_APBCMASK_SDADC;

Next, we configure asynchronous clock GCLK_1, which will be used as the clock for sampling the analog input. In this example, it is configured for 1 MHz:

// Set GCLK1 to use OSC48M (divided by 12.... 4 MHz)
GCLK->GENCTRL[1].bit.SRC = GCLK_GENCTRL_SRC_OSC48M_Val;

// Wait for synchronization
while(GCLK->SYNCBUSY.bit.GENCTRL1);

// Divide by 4... 1 MHz
GCLK->GENCTRL[1].bit.DIV = 4;

// Wait for synchronization
while(GCLK->SYNCBUSY.bit.GENCTRL1);

// Enable GCLK1
GCLK->GENCTRL[1].bit.GENEN = 1;

// Wait for synchronization
while(GCLK->SYNCBUSY.bit.GENCTRL1);

// Enable Output Pin (optional - to verify GCLK_1 frequency)
GCLK->GENCTRL[1].bit.OE = 1;

// Wait for synchronization
while(GCLK->SYNCBUSY.bit.GENCTRL1);

Finally, we select GCLK_1 as the clock source for the SDADC module, noting from the SAM C21 datasheet that the SDADC module is enumerated as Peripheral Channel 35:

SDADC module enumerated as Peripheral Channel 35

Also, update the appropriate peripheral channel control register as shown here:

// Enable GCLK1 to drive the SDADC
GCLK->PCHCTRL[35].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN_GCLK1;
Configure I/O Pins & PMUX

The first thing is to drive the output that is connected to the ADC- input to 0 V:

#define NEGDRV    PORT_PB13

// Drive negative side of SDADC input
PORT->Group[1].DIRSET.reg = NEGDRV;

// Negative side is driven low, to provide GND reference to light sensor
PORT->Group[1].OUTCLR.reg = NEGDRV;

Next, set the ADC+ and ADC- pins as inputs and use the PMUX to select the SDADC function:

Select ADC+ and ADC- pins  and PMUX

The following code sample performs this assignment:

#define SDADCIN1P PORT_PB09
#define SDADCIN1P_PIN  (PIN_PB09 & 0x1F)
#define SDADCIN1N PORT_PB08
#define SDADCIN1N_PIN  (PIN_PB08 & 0x1F)

// Disable Output Driver on the ADC+ pin
PORT->Group[1].DIRCLR.reg = SDADCIN1P;

// Use the PMUX to select the function for the ADC+ pin
PORT->Group[1].PINCFG[SDADCIN1P_PIN].reg = PORT_PINCFG_PMUXEN;

// Disable Output Driver on the ADC- pin
PORT->Group[1].DIRCLR.reg = SDADCIN1N;

// Use the PMUX to select the function for the ADC- pin
PORT->Group[1].PINCFG[SDADCIN1N_PIN].reg = PORT_PINCFG_PMUXEN;

// Choose SDADC function for the ADC+ and ADC- pins
PORT->Group[1].PMUX[(SDADCIN1N_PIN>>1)].reg = PORT_PMUX_PMUXO(GPIO_PIN_FUNCTION_B) |
PORT_PMUX_PMUXE(GPIO_PIN_FUNCTION_B);
Sampling Frequency (CLK_SDADC)

To configure the SDADC module, several registers require the module to be disabled to write to them, so clear the CTRLA.Enable bit. You must wait for synchronization on this bit, otherwise register writes you make after that may not be accepted:

// Turn off module to allow configuration
SDADC->CTRLA.bit.ENABLE = 0;

// Wait for synchronization
while(SDADC->SYNCBUSY.bit.ENABLE);

Register CTRLB holds settings for the Skip Count (which must be higher than 3), Prescaler (which is set to provide a CLK_SDADC (FS) of 125 kHz), and Oversampling Ratio (which is set to 1024 to provide OSR of ~122 samples/second):

// Number of samples to skip when module is started
SDADC->CTRLB.bit.SKPCNT = 4;

// Set CLK_SDADC to GCLK_1 / 2 (= 1 MHz  / 2 = 500 kHz)
// Set FS = CLK_SDADC / 4 = 125 kHz
SDADC->CTRLB.bit.PRESCALER = 0;

// Oversampling Ratio - 1024
// Samples come out at FS/1024 = 125 kHz / 1024 (~122 Hz)
SDADC->CTRLB.bit.OSR = OSR1024;
Set VREF

The conversion is performed on a full range between 0 V and the reference voltage. Analog inputs between these voltages convert to values based on a linear conversion.

Bandgap and SADC Diagram

To set the voltage reference, you must set two different fields in REFCTRL:

REFCTRL.REFSEL[1:0] selects the source for the reference. For this example, the light sensor output voltage covers the full range from VDD to GND, so we want to set VREF to VDDANA (3.3 V).

REFCTRL.REFRANGE[1:0] needs to be set based on the voltage level used for the reference. The required setting is the 2.4 V - 3.6 V value since VDDANA is 3.3 V.

REFCTRL.REFRANGE[1:0] set based on the voltage level

The following code sample performs this assignment:

// Vref = VCC
SDADC->REFCTRL.bit.REFSEL = SDADC_REFCTRL_REFSEL_INTVCC_Val;

// 2.4v < Vref < 3.6v
SDADC->REFCTRL.bit.REFRANGE = 2;
Step 1.5 Set Correction Values

Typically, you would measure a GND input voltage level and the value you receive from that would be used to set the offset correction value in OFFSETCORR), then you would measure a full-scale input value and use that to set the Gain correction value (in GAINCORR. To keep this example simple, we did NOT do any of that. The result is calculated in a 24-bit value, but the output is really only 16-bit, so we will use the shift correction register (SHIFTCORR) to automatically shift the result down by eight bits into a 16-bit field:

// Set Gain Correction to 1
SDADC->GAINCORR.reg = 1;

// Wait for synchronization
while(SDADC->SYNCBUSY.bit.GAINCORR);

// Set Offset Correction to 0
SDADC->OFFSETCORR.reg = 0;

// Wait for synchronization
while(SDADC->SYNCBUSY.bit.OFFSETCORR);

// Set Shift Correction to 8:  24-bit->16-bit
SDADC->SHIFTCORR.reg = 8;

// Wait for synchronization
while(SDADC->SYNCBUSY.bit.SHIFTCORR);
Set Input Channel

There are three differential inputs available to the SDADC, so we need to select which pair is connected to the module via the INPUTCTRL register.

Differential inputs Diagram

In this example, the Light Sensor signal is connected to input "1" on the mux (AIN1):

// Set Input Mux to AIN1
SDADC->INPUTCTRL.bit.MUXSEL = 1;

// Wait for synchronization
while(SDADC->SYNCBUSY.bit.INPUTCTRL);
Enable The Module

At this point, the SDADC module is configured, so now the module can be enabled:

// Turn on module
SDADC->CTRLA.bit.ENABLE = 1;

// Wait for synchronization
while(SDADC->SYNCBUSY.bit.ENABLE);

Reading Results
Start Conversion

Now the SDADC module can be easily triggered to sample and convert the input voltage. To do this, first, you need to clear the INTFLAG.RESRDY flag.

INTFLAG.RESRDY flag

// Clear Interrupt Flags
SDADC->INTFLAG.reg = 0;

Then you set the SWTRIG.START bit to trigger the conversion.

SWTRIG.START bit

// Trigger SDADC Conversion
SDADC->SWTRIG.bit.START = 1;
Wait for RESRDY Flag

Then wait for the INTFLAG.RESRDY bit to be set, indicating that the result is ready.

// Wait for Conversion to Complete
while (!SDADC->INTFLAG.bit.RESRDY);
Read/Shift the Result

Finally, read the SDADC result from the RESULT register into a variable. Recall in this example, we configured the SDADC module to automatically shift the raw 24-bit result into a 16-bit result, so we do not need to perform any shifting in our application.

The following code sample adds a NOP() instruction, so that you can set a breakpoint there to check the converted value:

// Read result
result = SDADC->RESULT.reg;

// Place Breakpoint Here
__NOP();

Learn More