Noisy ADC data from Xiao SAMD21

Hi,
In Xiao SAMD21, I designed a PCB to measure Voltage using Xiao SAMD21 internal ADC. But always getting Noise data details are attached below. In program

ADC set to 12 bit mode.
Using analogRead function.

Tried many ways to clear the noise, none of them are not working. Pls suggest a way to solve.

Captured serial data:
DATA_SAMPLES.txt (6.1 KB)

DATA_SAMPLES

Could be a problem with your code, or how you have the board wired up.

You must remember that when measuring a voltage up to 3.3V with 12 bit resolution means that a change of 1 in the reading from the adc is less than 1mV. That's a very small voltage! So it's very difficult to build a circuit that will not have this problem. If you are using a breadboard or flying wires, you will not be able to do it. Maybe if you design a custom PCB with great care and expertise, you can almost remove the noise.

One way to easily remove some of the noise is to take many readings from the adc and take the average. Another way is to put a small capacitor between the input pin and ground. Both of these ways will mean you cannot measure fast-changing signals.

There is no lose wiring. Actually I designed a PCB for it.
Still I am getting Noise of Arduino 10-40mV.

Even in implemented average mechanism still I am to get out this noise problem

I've heard some of the SAMD boards have inherent noise issues. I don't know from experience. Do you have a capacitor right at the analog in pin?

I read an article some time ago ( don't recall the platform) that suggested stopping some hardware functions that could be making noise (PWM, timers etc). I don't know if that would help here.

Hi @jaishankarm

The Seeeduino Xiao SAMD21 board makes design compromises to obtain its small footprint. One of these compromises, is that the analog voltage supply (VDDANA) is contected directly to the digital supply (VDDIOx) pins and omits the ferrite bead filtering recommended by the SAMD21 datasheet.

The board also provides no possiblity of using an external precision voltage reference, since the AREF pin isn't broken out and easily accessible.

You could try switching the ADC to the microcontroller's internal 1V reference: AR_INTERNAL1V0, since this is derived from the stable on-chip bandgap voltage reference, but his means limiting your analog input range to between 0V and 1V.

Thanks for your support.

Can I use any other SAMD21 based product like Arduino zero?

You have provided this data but no indication I can see of the expected range of input voltage - nor even what signal your data represents.
Here is the data in a sensible graph


Your "signal" seems to change at about 300 and 850 in the graph.
If the signal is representative of what you want to measure you NEED to do some signal conditioning before attempting to measure it.

Before you even connect it to an ADC you need to decide the range and frequency characteristics of your signal and the accuracy and precision to which you need to measure it.
Read this:

1 Like

Possibly, but it would depend on the application.

We havent seen the circuit diagram; also need to know the characteristics of the signal you hope to measure.

J1 is connected to 12V power supply(3S lithium ion battery)
Now I mounted C44 capacitor & continue the testing.

Result was noisy again.

Hi @jaishankarm

If you don't require a super high sample rate, you could try accumulating the result over a number of samples then averaging, to reduce noise.

The code example below sets-up the ADC just like analogRead() on A0, however it's also configured to accumulate over 256 samples. This generates a 20-bit value which the ADC auto shifts to the right by 4 (divide by 16), giving a 16-bit result. Setting the AVGCTRL.ADJRES to 0x4, shifts the 16-bit value to the right by a further 4 bits, giving an averaged 12-bit result:

// Set-up the ADC to read analog input on A0 averaged over 256 samples

// The signal is accululated over 256 samples, auto shifted right by 4 generating a 16-bit value
// This value is then divided by 4 to create an averaged 12-bit result

void setup() {
  SerialUSB.begin(115200);                         // Set-up the native serial port at 115200 bit/s
  while(!SerialUSB);                               // Wait for the console to open
  // Selecting the port multiplexer the the ADC doesn't seem necessary on the SAMD21
  //PORT->Group[PORTA].PINCFG[2].bit.PMUXEN = 1;              // Enable the A0 pin multiplexer
  //PORT->Group[PORTA].PMUX[2 >> 1].reg |= PORT_PMUX_PMUXE_B; // Switch the multiplexer to the ADC input
  ADC->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV512 |    // Divide Clock ADC GCLK by 512 (48MHz/512 = 93.7kHz)
                   ADC_CTRLB_RESSEL_16BIT;         // Set ADC resolution to either 16-bit accumulation/averaging mode
  while(ADC->STATUS.bit.SYNCBUSY);                 // Wait for synchronization
  ADC->AVGCTRL.reg = ADC_AVGCTRL_ADJRES(0x4) |     // Divide the accumulated 16-bit value by 4 generating an averaged 12-bit result
                     ADC_AVGCTRL_SAMPLENUM_256;    // Accumulates 256 samples generating 16-bit value
  ADC->SAMPCTRL.reg = 0x3f;                        // Set max Sampling Time Length to half divided ADC clock pulse times SAMPCTRL (341.33us)
  ADC->INPUTCTRL.bit.MUXPOS = 0x0;                 // Set the analog input to A0
  while(ADC->STATUS.bit.SYNCBUSY);                 // Wait for synchronization
  ADC->CTRLA.bit.ENABLE = 1;                       // Enable the ADC
  while(ADC->STATUS.bit.SYNCBUSY);                 // Wait for synchronization
}

void loop() {
  ADC->SWTRIG.bit.START = 1;                       // Initiate a software trigger to start an ADC conversion
  while(ADC->STATUS.bit.SYNCBUSY);                 // Wait for write synchronization
  while(!ADC->INTFLAG.bit.RESRDY);                 // Wait for the conversion to complete
  ADC->INTFLAG.bit.RESRDY = 1;                     // Clear the result ready (RESRDY) interrupt flag
  while(ADC->STATUS.bit.SYNCBUSY);                 // Wait for read synchronization
  int result = ADC->RESULT.reg;                    // Read the ADC result
  SerialUSB.println(result);                       // Output the result
  delay(500);                                      // Wait for 500ms
}

The SAMD21's datasheet gives more information on how to adjust these register values.

What is the purpose of this measurement?
Can we see a photo of your setup please? The "noise" may be coming from somewhere else.
Are there any other connections?
Are you keeping analog, digital and load grounds seperate?
What is the battery being used for?

After implementing this averaging & sampling rate in code my noise problem is resolved.
this is the graph of ADC->RESULT.reg value.

Hi @jaishankarm

The code above sacrifices sample rate for stability with the averaged sample time being 87ms or so.

The code example above will block, waiting for the ADC to complete.

The code example below however uses the ADC's interrupts to implement a non-blocking method, where the loop() function doesn't wait for the results:

// Set-up the ADC to read analog input on A0 averaged over 256 samples, non-blocking

// The signal is accululated over 256 samples, auto shifted right by 4 generating a 16-bit value
// This value is then divided by 4 to create an averaged 12-bit result

volatile uint16_t adcResult;                       // ADC result
volatile bool resultsReady = false;                // Results ready flag

void setup() {
  SerialUSB.begin(115200);                         // Set-up the native serial port at 115200 bit/s
  while(!SerialUSB);                               // Wait for the console to open
  // Selecting the port multiplexer the the ADC doesn't seem necessary on the SAMD21
  //PORT->Group[PORTA].PINCFG[2].bit.PMUXEN = 1;              // Enable the A0 pin multiplexer
  //PORT->Group[PORTA].PMUX[2 >> 1].reg |= PORT_PMUX_PMUXE_B; // Switch the multiplexer to the ADC input
  ADC->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV512 |    // Divide Clock ADC GCLK by 512 (48MHz/512 = 93.7kHz)
                   ADC_CTRLB_RESSEL_16BIT;         // Set ADC resolution to either 16-bit accumulation/averaging mode
  while(ADC->STATUS.bit.SYNCBUSY);                 // Wait for synchronization
  ADC->AVGCTRL.reg = ADC_AVGCTRL_ADJRES(0x4) |     // Divide the accumulated 16-bit value by 4 generating an averaged 12-bit result
                     ADC_AVGCTRL_SAMPLENUM_256;    // Accumulates 256 samples generating 16-bit value
  ADC->SAMPCTRL.reg = 0x3f;                        // Set max Sampling Time Length to half divided ADC clock pulse times SAMPCTRL (341.33us)
  ADC->INPUTCTRL.bit.MUXPOS = 0x0;                 // Set the analog input to A0
  while(ADC->STATUS.bit.SYNCBUSY);                 // Wait for synchronization
  NVIC_SetPriority(ADC_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority for the ADC to 0 (highest) 
  NVIC_EnableIRQ(ADC_IRQn);         // Connect the ADC to Nested Vector Interrupt Controller (NVIC)
  ADC->INTENSET.reg = ADC_INTENSET_RESRDY;         // Generate interrupt on result ready (RESRDY)
  ADC->CTRLA.bit.ENABLE = 1;                       // Enable the ADC
  while(ADC->STATUS.bit.SYNCBUSY);                 // Wait for synchronization
  ADC->SWTRIG.bit.START = 1;                      // Initiate a software trigger to start an ADC conversion
  while(ADC->STATUS.bit.SYNCBUSY);                // Wait for synchronization 
}

void loop()
{
  if (resultsReady)                                 // Check if the results are ready
  {                           
    SerialUSB.println(adcResult);                   // Display the results
    resultsReady = false;                           // Clear the resultsReady flag  
    ADC->SWTRIG.bit.START = 1;                      // Initiate a software trigger to start an ADC conversion
    while(ADC->STATUS.bit.SYNCBUSY);                // Wait for synchronization 
  }
}

void ADC_Handler()
{
  if (ADC->INTFLAG.bit.RESRDY)                      // Check if the result ready (RESRDY) flag has been set
  { 
    ADC->INTFLAG.bit.RESRDY = 1;                    // Clear the RESRDY flag
    while(ADC->STATUS.bit.SYNCBUSY);                // Wait for read synchronization
    adcResult = ADC->RESULT.reg;                    // Read the result;                          
    resultsReady = true;                            // Set the resultsReady flag
  }
}

Noted. I will definitely add this into my code.

Surely its better to eliminate the source of the noise than degrade the sample rate with a complicated averaging algorithm?

Hi all,
i have exact the same problem but using more than 1 pin for measurement.
I designed a PCB circuit and tested on breadboard and was hoping the noise could be excluded by the final design ( actually received first maufactured sample) and additional via the SAMD_AnalogCorrection.h (only offset).
I already implemented an array for multiple analogread() averaging but no nice result to me.
So this solution will be the right one for me.
I also have some interrupts for fast freq measurement added on pin A1.
But how can i use several pins as analog input with the XIAO?
I am using the pins A2,A9,A10 for permanent measurements (no sinus) .
The A7 and A8 also used as AI but only for the AnalogCorrection which is done before the final software is loaded.

How can i use the obove mentioned code with the 3 pins (A2,A9,A10)?

Best regards

Hannes

ADC->INPUTCTRL.bit.MUXPOS

You can change this register value(for the required pin) in cycle after each interrupt event trigger.