Hi everyone,
I'm working on a load cell project which requires high sampling rate and resolution. The aim is to read four load cells continuously as close to 1000Hz as possible using a single ADC. The data is being sent through the Serial for "real-time" plotting in a GUI.
So far, after research and experimentation, I have wired up a 32-bit ADC (TI ADS1262) breakout board to an Arduino UNO and four load cells. I'm able to read all four channels continuously as intended however theres a huge bottleneck in the data conversion from the SPI and only getting ~30Hz data rate on the serial port. I was hoping I could get some advice on how to improve this.
To help my case, I've summarised some of the relevant information:
-
ADS1262 datasheet: https://www.ti.com/lit/ds/symlink/ads1262.pdf
-
I am using the Protocentral library as reference, specifically this example: ProtoCentral_ads1262/1-Simple_Differential.ino at master · Protocentral/ProtoCentral_ads1262 · GitHub. The only real modification was cycling through the ADC channels within the loop. I also changed the .cpp file for the ADC settings I wanted.
-
The load cells are 500kg rated each, with 2mV/V sensitivity. Using a 5V supply from the Arduino and the PGA set at the maximum 32V/V gain, this means only 320mV of useful range so the resolution even with 32-bits is questionable and I will need to investigate further.
-
The ADC data rate is at 19.2kSPS, and the SPI settings are 4MHz (DIV4). The baud rate is at the 115200 maximum (haven't explored putty or anything yet).
Each loop in the arduino script is taking ~35ms to complete - that is 8ms per channel reading and another 2ms in printing values to the serial. Considering I want 1000Hz, this should be 1ms per loop, and the 35 factor reduction in computational time seems a bit scary to me!
I'm an aerospace engineering student so slowly learning this field and don't have sufficient C++ or SPI communication knowledge to understand exactly what is happening.
I've done research and found comparative code for other ADS chips, as well as issues relating to serial port baud rates, Serial.print() vs Serial.write() etc...
From what I've read, I think 1kHz should be very achievable and evidently the bottleneck is in the data conversion. I've attempted to dump the ADC byte readings onto the Serial but wasn't successful.
Again, unsure if this will be a sufficient improvement. Hoping someone might be able to guide me in the right direction with this
Here is the .ino code:
#include <SPI.h>
#include <SoftwareSerial.h>
#include <math.h>
#include <ads1262.h>
#define PGA 32 // Programmable Gain = 32
#define VREF 2.50 // Internal reference of 2.5V
#define VFSR VREF/PGA
ads1262 ADS; // ADS class
float force;
float volt_V;
float val1;
float val2;
float val3;
float val4;
volatile int i;
volatile char SPI_RX_Buff[10];
volatile long ads1262_rx_Data[10];
volatile static int SPI_RX_Buff_Count = 0;
volatile char *SPI_RX_Buff_Ptr;
volatile int Responsebyte = false;
volatile signed long sads1262Count = 0;
volatile signed long uads1262Count = 0;
double resolution = (double)((double)VREF / pow(2, 31)); // resolution = Vref/(2^n-1) , Vref = 2.5, n = no of bits
void setup()
{
// initalize the data ready and chip select pins:
pinMode(ADS1262_DRDY_PIN, INPUT); // data ready input line
pinMode(ADS1262_CS_PIN, OUTPUT); // chip enable output line
pinMode(ADS1262_START_PIN, OUTPUT); // start
pinMode(ADS1262_PWDN_PIN, OUTPUT); // Power down output
Serial.begin(115200); // Max Baud Rate
ADS.ads1262_Init(); // Initialise ads1262
}
void loop()
{
// Cycle through differential channels
val1 = read_channel(1);
val2 = read_channel(2);
val3 = read_channel(3);
val4 = read_channel(4);
// Print to serial
Serial.print(val1, 2);
Serial.print(",");
Serial.print(val2, 2);
Serial.print(",");
Serial.print(val3, 2);
Serial.print(",");
Serial.println(val4, 2);
}
// Read and convert adc binary data
float read_channel(int channel) // float
{
// For changing channels
switch (channel)
{
case 1: // Channel AN0/AN1
ADS.ads1262_Reg_Write(INPMUX, 0x01);
break;
case 2: // Channel AN2/AN3
ADS.ads1262_Reg_Write(INPMUX, 0x23);
break;
case 3: // Channel AN4/AN5
ADS.ads1262_Reg_Write(INPMUX, 0x45);
break;
case 4: // Channel AN6/AN7
ADS.ads1262_Reg_Write(INPMUX, 0x67);
break;
}
while ((digitalRead(ADS1262_DRDY_PIN)) != LOW)
{
continue;
}
if ((digitalRead(ADS1262_DRDY_PIN)) == LOW)
{
SPI_RX_Buff_Ptr = ADS.ads1262_Read_Data(); // read 6 bytes conversion register
for (i = 0; i < 5; i++) // Extract 4 bytes of data into buffer
{
SPI_RX_Buff[SPI_RX_Buff_Count++] = *(SPI_RX_Buff_Ptr + i);
}
ads1262_rx_Data[0] = (unsigned char)SPI_RX_Buff[1];
ads1262_rx_Data[1] = (unsigned char)SPI_RX_Buff[2];
ads1262_rx_Data[2] = (unsigned char)SPI_RX_Buff[3];
ads1262_rx_Data[3] = (unsigned char)SPI_RX_Buff[4];
// Get the raw 32-bit adc count out by shifting
uads1262Count = (signed long)(((unsigned long)ads1262_rx_Data[0] << 24) | ((unsigned long)ads1262_rx_Data[1] << 16) | (ads1262_rx_Data[2] << 8) | ads1262_rx_Data[3]);
volt_V = (resolution) * (float)uads1262Count; // voltage = resolution * adc count (V)
force = mapfloat(volt_V, 0.00, 0.32, 0.00, 500.00); // Map voltage to force (kg)
}
SPI_RX_Buff_Count = 0;
return force;
}
// Map values in float format
float mapfloat(float x, float in_min, float in_max, float out_min, float out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}