ADS1256 + RP2040 Custom DAQ

ADC

In my last video on this topic, I said that I finished this product because I managed to make a proper connection between the front panel and the PCB using a flexible PCB. In the meantime, I have been searching for alternative and better solutions because the cost of manufacturing these flexible PCBs is not exactly cheap. So, I tried to find something off the shelf that is mass-produced and therefore cheap. In this article, I show you the solution I found, explain a bit more about the board and demonstrate its capabilities and performance. I will also talk about the official Arduino library I made for this chip because there have been some improvements done on it as well.

 

Introduction

For those who haven’t been following this project, I want to start with a short introduction.

This is an 8-channel data acquisition (DAQ) board with a high-performance AD converter chip (ADS1256) and a high-performance microcontroller. The ADS1256 is an 8-channel, 24-bit analogue-to-digital converter chip that is capable of reaching up to 30kSPS output rates. It has fairly good performance, which makes it popular for projects that require multiple high-resolution channels. Originally, I developed this board with an ATmega32u4 microcontroller, but I quickly realised that despite its native USB, many GPIO ports and somewhat better performance than the “average Arduino board”, it can’t keep up with the ADS1256 at high sampling rates. Therefore, I transitioned the design to the RP2040. This is a much more powerful microcontroller with even more GPIOs, native USB support, a huge amount of (external) memory and more.

To ensure good performance, the ADS1256 receives its reference voltage from a buffered REF5025, a 2.5 V voltage reference chip. The buffering is provided by an OPA350 chip in, guess what, buffer/voltage follower mode. The ADS1256 has its own digital power supply (DVDD) provided by an XC6206P332MR voltage regulator.

The main circuit board has other convenient features such as easy access to the programming pins (SWCLK, SWD) of the RP2040 or the onboard 3.3 V line. Not the 3.3 V line of the ADS1256, but the separate one that feeds the rest of the circuit. There are also several test points on the circuit that allow the user to check the actual reference voltage value or to see if the 3.3 V for the ADS1256 is really 3.3 V.

Where it was possible or necessary, I used planes for power instead of just traces. This is especially valid around the USB supply and around the 3.3 V voltage regulator.

There are invisible features in the board as well. They are not visible because they are inside the board. This is because the board actually has four layers. On the top and the bottom layers, I run the signals and the power rails. I tried to separate them a bit, and for example, most of the 5 V lines are on the bottom side of the board; the 3.3 V and the voltage reference lines are on the top side. The two middle layers are both ground. This might not be accepted by some people, but after reading a lot of discussions on Stack Exchange and Reddit, it seems that the SIG/PWR-GND-GND-SIG/PWR layout is preferred by many people.

So, after hooking up the RP2040 with its external memory and the ADS1256, there are still 25 GPIOs left. They, and the three power pins: GND, 5 V and 3.3 V together, are 28 additional connections that can/must go somewhere. So, this started my initial work, where I made a front panel out of a PCB where one can access all these pins. But, at that time, I used a very cumbersome approach that needed a lot of very meticulous soldering and crimping. Then, some viewers suggested that I should look at some automotive connectors, and later, I ended up finally making the previously-mentioned flex PCB-based connector.

Now finally (and I swear), I settled with a solution. I found a short enough, off-the-shelf 28-pin flexible ribbon cable that fits the already existing connector on the main PCB. Furthermore, I found a much cheaper socket for the front panel on AliExpress as well. The only problem was that I had to reroute the front panel because the socket was mirrored compared to the originally used one. So, some of my earlier front panels don’t work with this cheaper type of connector.

With these changes, I finally dare to say that this project is finished, and I think it is mature enough to become a product.

 
 

Demonstration

To test whether the front panel works as it should, I did a very simple experiment. I wrote a short code that reads all the GPIOs that were made available on the front panel. It is just a simple digitalRead() function. Then, with the help of a small resistor (4.7k) I shorted the pins to 3.3 V one by one. When the shorted pin was recognised by the code, it printed the corresponding port number on the serial terminal. It was a bit tedious to go through all 25 pins, but it is part of the quality check process.

Thanks to the fact that the RP2040 has a very flexible way of handling the GPIOs, we have access to many extra features via the front panel pins. They are not only GPIOs, but they can be used as SPI, I2C or serial peripherals as well. In my earlier video, I already demonstrated this by connecting a display to the front panel and displaying some measurement data on it.

With a bit more flexible thinking, we can turn this DAQ into a remote logger by adding not only a display, but also some buttons and an SD card reader.

 

The earlier iteration of the GPIO front panel could already drive external devices.

 
 

Also, since the last video on this circuit, the Arduino library for the ADS1256 chip has gone through a lot of improvements.

For example, the SPI communication part of the library has been entirely reworked. This allows the user to assign any of the SPI peripherals of the microcontroller to the ADS1256. Previously, this was only possible by modifying the library, but now, the user can do it by just updating a few lines in the provided example code. All the SPI configuration-related lines are carefully documented in the library’s documentation, including the microcontroller-specific parts. I carefully tested the library with at least 10 different microcontrollers, such as Teensy 4.0, different ESP32 models, Arduino Uno, Nano and Mega, STM32 “blue pill” (F103C8T6) and “black pill” (F401CCU6) and so on. These boards are also in the example code with the necessary pin definitions and so on.

 

A few, but not all the MCUs that have been (successfully) tested for my ADS1256 Arduino library

 

Performance

Finally, let’s have a series of tests that demonstrate the capabilities of my board with the ADS1256 chip and the REF5025 voltage reference.

Noise floor test

In this test, I just shorted AIN+ and AIN- (A0 and A1) together and connected them to GND. Then I set the input to [A0+A1] differential input, set PGA = 1, and set the sampling rate to 100 SPS and collected 10000 samples.

The results are the following:

Parameter Value
Data Rate 100 SPS
Samples 10000
Mean -14.443 counts / -8.344 µV
Minimum -22 counts
Maximum -7 counts
Peak-to-peak noise 15 counts / 8.940 µV
RMS noise 1.865 counts / 0.596 µV

To see if decreasing the sampling rate helps with the noise, I performed the same experiment with 10 SPS. The board achieved a peak-to-peak noise of only 3.6 uV and an RMS noise of approximately 0.46 uV.

Parameter Value
Data Rate 10 SPS
Samples 10000
Mean -12.218 counts / -7.152 µV
Minimum -15 counts
Maximum -9 counts
Peak-to-peak noise 6 counts / 3.576 µV
RMS noise 0.766 counts / 0.456 µV

A rough estimate for the noise-free bits is about 21.4 bits at 10 SPS and PGA = 1. This is based on the 10 V range of the ADC (-5 V to +5 V) and the peak-to-peak noise of 3.576 uV.

Just as a comparison, the datasheet (Table 3) reports 22.3 noise-free bits at 10 SPS and PGA=1. Not too bad, I guess.

 
void runADCNoiseTest()
{
    const uint32_t samples = 100;

    long value;
    long minValue = 2147483647;
    long maxValue = -2147483648;

    double sum = 0;
    double sumSquares = 0;

    Serial.println();
    Serial.println("ADS1256 noise test started");
    Serial.println("Input should be shorted: AIN0 + AIN1 + GND");
    Serial.println();

    A.setMUX(DIFF_0_1);
    A.setPGA(PGA_1);
    A.setDRATE(DRATE_10SPS);

    delay(1000);

    for (uint32_t i = 0; i < samples; i++)
    {
        value = A.readSingle();

        if (value < minValue) minValue = value;
        if (value > maxValue) maxValue = value;

        sum += value;
        sumSquares += (double)value * (double)value;
    }

    double mean = sum / samples;

    double variance = (sumSquares / samples) - (mean * mean);
    double rmsNoiseCounts = sqrt(variance);

    long peakToPeakCounts = maxValue - minValue;

    double meanVoltage = A.convertToVoltage(mean);
    double rmsNoiseVoltage = A.convertToVoltage(rmsNoiseCounts);
    double peakToPeakVoltage = A.convertToVoltage(peakToPeakCounts);

    Serial.println("ADS1256 noise test finished");
    Serial.println("---------------------------");

    Serial.print("Samples: ");
    Serial.println(samples);

    Serial.print("Mean [counts]: ");
    Serial.println(mean, 3);

    Serial.print("Min [counts]: ");
    Serial.println(minValue);

    Serial.print("Max [counts]: ");
    Serial.println(maxValue);

    Serial.print("Peak-to-peak [counts]: ");
    Serial.println(peakToPeakCounts);

    Serial.print("RMS noise [counts]: ");
    Serial.println(rmsNoiseCounts, 3);

    Serial.println();

    Serial.print("Mean [V]: ");
    Serial.println(meanVoltage, 9);

    Serial.print("Peak-to-peak noise [V]: ");
    Serial.println(peakToPeakVoltage, 9);

    Serial.print("RMS noise [V]: ");
    Serial.println(rmsNoiseVoltage, 9);

    Serial.println("---------------------------");
}
 

Reference voltage measurement

I have an AD584-M voltage reference circuit. I measured its 2.5 V output using all the single-ended channels (SING_0 → SING_7) and all the differential channel pairs (DIFF_0_1 → DIFF_6_7).

The results are the following:

Test Mean Voltage Error Error % Pk-Pk Noise RMS Noise
SING_02.499001 V-0.999 mV-0.0399%20.264 µV2.384 µV
SING_12.499008 V-0.992 mV-0.0397%16.688 µV2.384 µV
SING_22.499016 V-0.984 mV-0.0393%20.860 µV2.384 µV
SING_32.499018 V-0.982 mV-0.0393%21.456 µV2.980 µV
SING_42.499020 V-0.980 mV-0.0392%16.092 µV2.384 µV
SING_52.499017 V-0.983 mV-0.0393%17.284 µV2.384 µV
SING_62.499019 V-0.981 mV-0.0393%14.900 µV2.384 µV
SING_72.499014 V-0.986 mV-0.0394%22.052 µV2.384 µV
DIFF_0_12.497985 V-2.015 mV-0.0806%24.435 µV4.172 µV
DIFF_2_32.498091 V-1.909 mV-0.0764%34.567 µV7.152 µV
DIFF_4_52.498108 V-1.892 mV-0.0757%36.355 µV7.748 µV
DIFF_6_72.498134 V-1.866 mV-0.0746%40.527 µV6.556 µV

As we can see, the single-ended measurements are remarkably consistent. The differential channels are not that consistent and they report a slightly different value as well. This could be due to several reasons. The single-ended and the differential measurements require different input paths. This essentially doubles the contribution of input leakage, source impedance effects, settling errors…etc. Also, if there’s an imperfection in the common-mode rejection, the differential mode picks it up. Furthermore, source impedance imbalance can also occur due to the fact that while AIN+ was directly connected to the source, AIN- was connected to the ground and these two nodes might have different impedance. Also, initially, the performance was somewhat worse in the beginning. So much worse that I disassembled the enclosure and cleaned the board. As it turned out, the flux residue “showed up” in the measurements and offset the A0 input significantly. Probably the cleaning is still not sufficient because the DIFF_0_1 channel still shows a noticeably lower value than the others.

 

The height of the chart represents the absolute readings in Volts, and the numbers above the columns represent the absolute error in microVolts.

This area of the ADS1256 is extremely sensitive to contamination. Make sure to carefully clean up all the flux residues!

 
 

PGA gain comparison

I used a small signal from the same AD584-M reference I used above by dividing its 2.5 V output with a 100k:1k resistor ladder, and applied different PGA (gain) values to see how the board can resolve tiny signals at different gain settings. The signal should be roughly 25 mV, which is ideal for testing all PGA settings between 1 and 64. At PGA = 64, the highest measurable signal is 78.125 mV, so the expected ~25 mV is well within this range.

The results are the following:

PGA Mean Voltage Mean Counts Pk-Pk Noise RMS Noise
PGA_124.322819 mV40,81142.315 µV10.728 µV
PGA_224.323711 mV81,62530.097 µV9.536 µV
PGA_424.321180 mV163,23430.395 µV12.665 µV
PGA_824.318125 mV326,42628.309 µV9.983 µV
PGA_1624.312016 mV652,68737.882 µV11.138 µV
PGA_3224.308532 mV1,305,18536.430 µV12.516 µV
PGA_6424.308644 mV2,610,38329.315 µV12.153 µV

The difference between PGA_1 and PGA_64 is tiny, only about 14 uV. So, the measured voltage stays almost constant from PGA_1 to PGA_64. This means that the PGA scaling works correctly.

 

It is worth noting that the shown distance along the Y-axes is only 17 uV!

 
 

Data rate vs noise test

I used the same voltage reference circuit as before and measured its output at different sampling rate values.

Data Rate Samples Mean Pk-Pk Noise RMS Noise
30000 SPS10000-14.696 counts / -8.344 µV313 counts / 186.544 µV29.134 counts / 17.284 µV
7500 SPS10000-26.423 counts / -15.496 µV136 counts / 81.054 µV17.524 counts / 10.132 µV
1000 SPS10000-10.240 counts / -5.960 µV52 counts / 30.991 µV6.768 counts / 3.576 µV
100 SPS10000-12.795 counts / -7.152 µV16 counts / 9.536 µV2.100 counts / 1.192 µV
10 SPS1000-12.623 counts / -7.152 µV6 counts / 3.576 µV1.027 counts / 0.596 µV
2 SPS1000-11.634 counts / -6.556 µV4 counts / 2.384 µV0.839 counts / ~0.500 µV

As we can see, decreasing the sampling rate greatly improves the noise performance. The measurement confirms the practical tradeoff described in the datasheet: the ADS1256 can be configured either for high-speed measurements or for lower-noise, higher-resolution measurements by changing the digital filter setting through the DRATE register.

 

The height of the columns represents the RMS noise, and the numbers above the columns represent the peak-to-peak noise in microVolts.

 

Differential polarity verification

Since the ADS1256 can perform true bipolar differential measurements, I wanted to test it as well. I used the same resistor-divided 2.5 V voltage reference (~25 mV output) as I used in one of the earlier tests. I connected the output of the divider to A0 and the GND to A1. I measured 1000 samples at 100 SPS data rate and evaluated the noise parameters. Then I flipped the wires so that A1 measured the output of the resistor divider and A0 was connected to GND, and I measured the same parameters.

The original wiring gave about 24.31 mV, and after reversing the connections, the value changed to approximately -24.32 mV. I repeated the test several times, and the difference between measurements remained within 10-15 uV. This shows good symmetry and repeatability.

 
 
 

"Real-life” demo with a load cell

I connected the output of a 100 g load cell directly to the input of the board to show how it works. The excitation voltage was provided by the previously used AD584-M chip at 5 V. Then, as the table below shows, I applied different loads and read the corresponding counts. The data rate was set to 10 SPS, and the PGA was set to the highest value: 64. 20 Samples were collected through the A0+A1 differential input.

Load Mean Counts Net Counts RMS Noise Mean Voltage
0 g-10055.070057.627 counts-93.635 µV
10 g24738.50034793.5709.929 counts230.368 µV
20 g59216.37069271.44010.849 counts551.437 µV
50 g162833.770172888.84010.323 counts1516.349 µV
100 g335326.440345381.51025.279 counts3122.654 µV
0 g repeat-9558.480496.5908.764 counts-89.007 µV

The measured data points were plotted with the applied weight (grams) on the X-axis and the corresponding mean ADC counts on the Y-axis. A linear fit was then applied to the dataset. The slope of the fitted line (parameter A) represents the sensitivity of the system in counts per gram, while the intercept (parameter B) represents the zero-offset count value.

By rearranging the linear equation and substituting the measured ADC counts, the applied weight can be calculated directly in grams. The resulting calibration curve showed good linearity over the tested range of 0 to 100 grams. This demonstrates the suitability of the ADS1256 and load-cell combination for precision weighing applications.

It is worth noting that the calibration parameter A remains fixed once a calibration procedure has been performed; however, the zero-offset value can be updated at any time by taring the scale. This is important because updating the zero-offset removes errors caused by load-cell drift, the mounting hardware, temperature changes, and other environmental influences. As a result, the scale can maintain accurate measurements without requiring a complete recalibration. Only the reference point is adjusted, while the sensitivity of the system (counts per gram) remains unchanged.

 

Additional content

 
PCB from PCBWay

*My 4-layer PCB with the FPC connector and front panel are not shared on purpose. If you want one of those, contact me using the contact form on my website.

 

Next
Next

AliExpress Finds - Part 1