RobTillaart / ADS1X15

Arduino library for ADS1015 = I2C 12 bit ADC and ADS1115 = I2C 16 bit ADC
MIT License
155 stars 29 forks source link

Read 4 channel asynchronous #24

Closed Mplex72 closed 3 years ago

Mplex72 commented 3 years ago

Hi ,

How can I read 4 channels asynchronus ? The problem is the readout for each value see line below ;

 int16_t value = ADS.getValue(i); 

and gives the following error ; "invalid types 'int16_t {aka int}[int]' "

I have the followiing sketch to test ;


//

#include "ADS1X15.h"

ADS1115 ADS(0x48);
float f = 0;

void setup()
{
  Serial.begin(115200);
  Serial.println(__FILE__);
  Serial.print("ADS1X15_LIB_VERSION: ");
  Serial.println(ADS1X15_LIB_VERSION);

  ADS.begin();
  ADS.setGain(0);
  f = ADS.toVoltage();      // voltage factor
  ADS.requestADC(0);
}

void loop()
{
  if (ADS.isBusy() == false)
  {
    for (int i = 0; i < 4; i++) {
      int16_t value = ADS.getValue(i);
      ADS.requestADC(i);  // request a new one
      Serial.print("\tAnalog0: ");
      Serial.print(value[i]);
      Serial.print('\t');
      Serial.println(value[i] * f, 3);
    }
  }
  // simulate other tasks...
  delay(2000);
}

// -- END OF FILE --

Klass

RobTillaart commented 3 years ago

The ADS1115 has only one ADC in it that is multiplexed. This means an application cannot request channel 2 while acquiring channel 1
So you have to wait until a sample is made.

The trick used in the last sketch is to run 4 devices in parallel. That keeps the waiting down.

Apparently you have a requirement that the samples need to be made in the same window of 10 msec or less. Is that correct?

Do you have a hard 16 bit requirement or would 12 bit (ADS1015) be sufficient?

For better performance you should remove all the print statements (except printing the read values)

idea for improvement Now the code checks the isBusy() of every device. That takes time. If you know how much time it takes you can just set a clock and after e.g 2000 (to be determined) microseconds start with reading the sensors. Checking micros() takes a few microseconds while checking isBusy() takes microseconds. Given that the code might check four of them, this adds up

Give this a try

bool ADS_read_all()
{
  static uint32_t lastTime = 0;
  if (micros() - lastTime < 2000) return true;
  lastTime = micros();

  val0[idx] = ADS0.getValue();
  val1[idx] = ADS1.getValue();
  idx++;
  if (idx < 4)
  {
    ADS_request_all();
    return true;
  }
  idx = 0;
  return false;
}
RobTillaart commented 3 years ago

I had the idea to sample (low) datarate@475 SpS for lowest possible noise that should be enough for 100SpS for 4 values

Question: Sampling does that include the selection of the channel and the transport of the sampled value?

Mplex72 commented 3 years ago

I had the idea to sample (low) datarate@475 SpS for lowest possible noise that should be enough for 100SpS for 4 values

Question: Sampling does that include the selection of the channel and the transport of the sampled value?

Yes , I tought to get the whole cyclus within 9 or 10 millis. and yes preferably all 16 values within the same 10 millis sampled. 100Hz output for all 16values in the Can messages.

I can go to the ads1015 when I keep the measurement range smaller. the last bits is perhaps only noise

RobTillaart commented 3 years ago

If you really need to do all the samples in a small timeframe and 12 bit is sufficient, you might consider my MCP_ADC library in combination MCP3208. It is a SPI device that can read quite fast, my tests on an UNO about 8 channels in 1 millisecond. It is however not that precise...

Mplex72 commented 3 years ago

If you really need to do all the samples in a small timeframe and 12 bit is sufficient, you might consider my MCP_ADC library in combination MCP3208. It is a SPI device that can read quite fast, my tests on an UNO about 8 channels in 1 millisecond. It is however not that precise...

with 1 milli times I can average over 3 or 4 samples to get very usefull 12 bits ?

Mplex72 commented 3 years ago

doing the loop in , 11 millis



ADS_print_all
41412   -1  -1  1   1   -1  -1  -1  -1  
ADS_request_all
loop
ADS_request_all
ADS_request_all
ADS_request_all
ADS_print_all
42422   -1  -1  0   0   -2  -2  -1  -1  
ADS_request_all
loop
ADS_request_all
ADS_request_all
ADS_request_all
ADS_print_all
43431   1   1   -1  -1  -1  -1  -2  -2  
ADS_request_all
loop
ADS_request_all
ADS_request_all
ADS_request_all
ADS_print_all
RobTillaart commented 3 years ago

If you really need to do all the samples in a small timeframe and 12 bit is sufficient, you might consider my MCP_ADC library in combination MCP3208. It is a SPI device that can read quite fast, my tests on an UNO about 8 channels in 1 millisecond. It is however not that precise...

with 1 milli times I can average over 3 or 4 samples to get very usefull 12 bits ?

In theory that should work. .

RobTillaart commented 3 years ago

What is this last run with all the -1 and -2?


Answer: update that is GND level fluctuating

Mplex72 commented 3 years ago

Sorry , I have it now running with input signals This is not working as it gives no or random output . the older version from the beginning are also not working

RobTillaart commented 3 years ago

This is not working as it gives no or random output .

what are you referring to with this ? I cannot see what you see...

Mplex72 commented 3 years ago

it are zero values from the voltage divider

RobTillaart commented 3 years ago

voltage divider?

Mplex72 commented 3 years ago

its the zero value drifting around (16th bit is minus signal)

Mplex72 commented 3 years ago

this is voltage 5volt on ADC1 channel 3



35378   25  25  19  19  -1  -1  -2  -2  
ADS_request_all
loop
IDX:    0
ADS_request_all
IDX:    1
ADS_request_all
IDX:    2
ADS_request_all
IDX:    3
ADS_print_all
36387   28  28  20  20  -1  -1  -3  -3  
ADS_request_all
Mplex72 commented 3 years ago

5volt signal on ADC2 channel2


ADS_request_all
loop
IDX:    0
ADS_request_all
IDX:    1
ADS_request_all
IDX:    2
ADS_request_all
IDX:    3
ADS_print_all
131359  -2  -2  0   0   11117   11117   0   0```

with the isbusy check working 

```cpp

bool ADS_read_all()
{
 // if (ADS0.isBusy() || ADS1.isBusy()) return true;
  Serial.print("IDX:\t");
  Serial.println(idx);
  val0[idx] = ADS0.getValue();
  val1[idx] = ADS1.getValue();
  idx++;
  if (idx < 4)
  {
    ADS_request_all();
    return true;
  }
  idx = 0;
  return false;
Mplex72 commented 3 years ago

This is the output with a normal read (ADS-read) from the lib voltage dider active.


Analog0: -1 -0.000
    Analog1: 92 0.017
    Analog2: 11095  2.080
    Analog3: 10 0.002

    Analog0: 4  0.001
    Analog1: 91 0.017
    Analog2: 11098  2.081
    Analog3: 7  0.001

    Analog0: 3  0.001
    Analog1: 89 0.017
    Analog2: 11133  2.088
    Analog3: 6  0.001

    Analog0: 3  0.001
    Analog1: 90 0.017
    Analog2: 11110  2.083
    Analog3: 12 0.002
RobTillaart commented 3 years ago

I do not understand your last 6 post.

Is the problem solved now?

Mplex72 commented 3 years ago

Hi Rob,

Having problems with the read buffers ,
when running the sketch with real data it does not refresh the buffers. no faults when compiling and don,t know it,s software or hardware related yet. still puzzling to get it working as this should.

Take some time so close this for now ?

Klass

RobTillaart commented 3 years ago

I have ordered 4x 1115 + 4x 1015, expect them Wednesday. So if time permits I can recreate the setup (without can-bus) later this week.

Mplex72 commented 3 years ago

Would be fine to have this working . it is a pretty efficient method

RobTillaart commented 3 years ago

The sensors are in, I had my soldering moment so made this setup

4x_ADS1115

RobTillaart commented 3 years ago

With the green wire (GND) on the right I checked every input and reading 4 ADS1115 in parallel works.

//
//    FILE: ADS_async_8_channel.ino
//  AUTHOR: Rob Tillaart
// VERSION: 0.1.0
// PURPOSE: demo reading two ADS1115 modules in parallel
//    DATE: 2021-07-05
//     URL: https://github.com/RobTillaart/ADS1X15

// Note all IO with the sensors are guarded by an isConnected()
// this is max robust, in non critical application one may either
// cache the value or only verify it in setup (least robust).
// Less robust may cause the application to hang - watchdog reset ?

#include "ADS1X15.h"

ADS1115 ADS0(0x48);
ADS1115 ADS1(0x49);
ADS1115 ADS2(0x4A);
ADS1115 ADS3(0x4B);

int16_t val0[4] = { 0, 0, 0, 0 };
int16_t val1[4] = { 0, 0, 0, 0 };
int16_t val2[4] = { 0, 0, 0, 0 };
int16_t val3[4] = { 0, 0, 0, 0 };
int     idx = 0;

void setup()
{
  Serial.begin(115200);
  Serial.println(__FILE__);
  Serial.print("ADS1X15_LIB_VERSION: ");
  Serial.println(ADS1X15_LIB_VERSION);

  ADS0.begin();
  ADS1.begin();
  ADS2.begin();
  ADS3.begin();
  Serial.println(ADS0.isConnected());
  Serial.println(ADS1.isConnected());
  Serial.println(ADS2.isConnected());
  Serial.println(ADS3.isConnected());
  idx = 0;
  ADS_request_all();
}

void loop()
{
  Serial.println(__FUNCTION__);
  // wait until all is read...
  while (ADS_read_all());

  // we have all 8 values
  ADS_print_all();

  delay(1000);      // wait a second.
  ADS_request_all();
}

void ADS_request_all()
{
  // Serial.println(__FUNCTION__);
  if (ADS0.isConnected()) ADS0.requestADC(idx);
  if (ADS1.isConnected()) ADS1.requestADC(idx);
  if (ADS2.isConnected()) ADS2.requestADC(idx);
  if (ADS3.isConnected()) ADS3.requestADC(idx);
}

bool ADS_read_all()
{
  if (ADS0.isConnected() && ADS0.isBusy()) return true;
  if (ADS1.isConnected() && ADS1.isBusy()) return true;
  if (ADS2.isConnected() && ADS2.isBusy()) return true;
  if (ADS3.isConnected() && ADS3.isBusy()) return true;
  //Serial.print("IDX:\t");
  //Serial.println(idx);
  if (ADS0.isConnected()) val0[idx] = ADS0.getValue();
  if (ADS1.isConnected()) val1[idx] = ADS1.getValue();
  if (ADS2.isConnected()) val2[idx] = ADS2.getValue();
  if (ADS3.isConnected()) val3[idx] = ADS3.getValue();
  idx++;
  if (idx < 4)
  {
    ADS_request_all();
    return true;
  }
  idx = 0;
  return false;
}

void ADS_print_all()
{
  // Serial.println(__FUNCTION__);
  // TIMESTAMP
  Serial.println(millis());

  // PRINT ALL VALUES OF ADC0
  for (int i = 0; i < 4; i++)
  {
    Serial.print(val0[i]);
    Serial.print("\t");
  }
  // PRINT ALL VALUES OF ADC1
  for (int i = 0; i < 4; i++)
  {
    Serial.print(val1[i]);
    Serial.print("\t");
  }
  Serial.println();
  // PRINT ALL VALUES OF ADC2
  for (int i = 0; i < 4; i++)
  {
    Serial.print(val2[i]);
    Serial.print("\t");
  }
  // PRINT ALL VALUES OF ADC3
  for (int i = 0; i < 4; i++)
  {
    Serial.print(val3[i]);
    Serial.print("\t");
  }
  Serial.println();
}

// -- END OF FILE --
RobTillaart commented 3 years ago

Output

ADS1X15_LIB_VERSION: 0.3.1
1
1
1
1
loop
62
2904    2946    3044    2850    2924    2979    3065    2880    
2883    2934    -1  2917    2902    2973    3047    2946    
loop
1122
2880    2950    3021    2942    2893    2970    3035    2999    
2874    2935    -1  2996    2910    2955    3021    3041    
loop
2183
2903    2925    2985    3023    2941    2937    3002    3067    
2914    2911    -1  3052    2950    2925    2987    3101    

The -1 is where I connected GND.

A loop takes 1061 millis of which 1000 is a delay, so total time = 61/62 millis ==> requesting + reading of 4 x 4 analog channels takes (rounded) 64 millis ==> 16 milliseconds for 4 devices in parallel.

RobTillaart commented 3 years ago

Made a PR with two new examples including the above.

Mplex72 commented 3 years ago

THAT IS FAST ! You,ve extracted the maximum out of this IC

Mplex72 commented 3 years ago

Now do some crashtest but so far it seems perfectly stable , great , really great. I think this issue is closed as far as I can see ?

Mplex72 commented 3 years ago

with the code in the push release not all values show zero. otherwise working perfect.

the code above with separate constructors is ok


1057
0   65534   0   0   
1   65533   65535   1   
1   65534   0   0   
0   65534   65535   0   

1057
1   65534   65535   0   
3   65534   0   0   
0   65533   0   0   
2   65535   65535   0   

1056
65535   65534   0   65535   
2   65534   0   65534   
1   65535   65535   1   
0   65534   65535   0   

1057
2   65534   0   1   
1   65534   0   65535   
0   65534   65535   0   
0   65534   65535   0   
RobTillaart commented 3 years ago

Note that the value should be a 16 bit signed int ==>15 bits + sign

65535 is just an unsigned way to write -1 (and that is close to zero last time I looked)

RobTillaart commented 3 years ago

Tweaked the code a bit and I got a few millis of

loop
1049
3181    2787    3028    3068    3241    2744    3026    3100    
-1  2850    2848    3330    3229    2873    2908    3301    

49 / 4 =~ 12 milliseconds per 4 samples (but the code looks not nice)

Mplex72 commented 3 years ago

Yers I know that its zero but in the logs it looking very eratic. May I see the fast code ? Is it with serial output after each 4 samples ?

RobTillaart commented 3 years ago

May I see the fast code ?

changed it already, It did 2 things

Need to recreate it.

MY current test uses the variation below uses the assumption that as ADS3 is started last, it will also be finished last.

bool ADS_read_all()
{
  //  if (ADS0.isBusy()) return true;
  //  if (ADS1.isBusy()) return true;
  //  if (ADS2.isBusy()) return true;

  if (ADS3.isBusy()) return true;
  val0[idx] = ADS0.getValue();
  val1[idx] = ADS1.getValue();
  val2[idx] = ADS2.getValue();
  val3[idx] = ADS3.getValue();

  //Serial.print("IDX:\t");
  //Serial.println(idx);

  idx++;
  if (idx < 4)
  {
    ADS_request_all();
    return true;
  }
  idx = 0;
  return false;
}

it scores 1054

RobTillaart commented 3 years ago

Also quite stable at 1056

bool ADS_read_all()
{
  if (ADS0.isBusy()) return true;
  val0[idx] = ADS0.getValue();     // fetch value as soon as possible.
  if (ADS1.isBusy()) return true;
  val1[idx] = ADS1.getValue();
  if (ADS2.isBusy()) return true;
  val2[idx] = ADS2.getValue();
  if (ADS3.isBusy()) return true;
  val3[idx] = ADS3.getValue();

  //Serial.print("IDX:\t");
  //Serial.println(idx);

  idx++;
  if (idx < 4)
  {
    ADS_request_all();
    return true;
  }
  idx = 0;
  return false;
}

With extra administration of the status per device the performance could be squeezed a bit more. However it would become quite complex and you still have to wait until the slowest device has made the 4 scans.

RobTillaart commented 3 years ago

To speed up the code you might add these 4 lines in setup() after the connected test

  ADS0.setDataRate(7);
  ADS1.setDataRate(7);
  ADS2.setDataRate(7);
  ADS3.setDataRate(7);

default dataRate = 4, 7 is fastest, but 7 allows more noise, so depending on the quality of your signals increasing the dataRate improves the performance.

A datarate of 4 gives 128 samples / second = 8 millisecond per sample. As we do 4 samples the theoretical minimum == 4x 8 = 32. Add to it the duration of the calls of request(), isBusy() and getValue() (e.g. 3x12 x 0.5 ms ??? => 18 ms makes 50 millis ==> so 56 millis is not bad at all.

Mplex72 commented 3 years ago

It is pretty maxed out this way ! The ADC is just slower as I was aware of because I didn,t read/understand it fully . I had it already on rate(6) and I have a capacitor on it to smooth the signal a little.

getting the 16bytes to the CANbus takes also 50 millis .... not able to find a faster way.

RobTillaart commented 3 years ago

getting the 16bytes to the CANbus takes also 50 millis .... not able to find a faster way.

Mind you, you have 16 values of 2 bytes each...

You need to read every detail of the data sheet to see what is possible. And then find a good / efficient library that can get the most from the IC used.

However the ADS part, think we can close this issue.

Mplex72 commented 3 years ago

indeed for the 32 bytes.

Yes , case closed thanks to your hard work , thank you .

RobTillaart commented 3 years ago

a last look at your canbus code, gave a few ideas how to optimize it

void ADS0_read() 
{
  byte stmp[32];    // assume 16 x 2 
  uint8_t idx = 0;

  for (uint8_t i = 0; i < 16; i++)
  {
    //  assume all values are in val[] array by the above code
    //  val[i] = val[i] * 0.1875;  //  ==> 0.1875 * 3 /16 
    //  ==>     x = x * 3/ 16  = x * 1/8 + x * 1/16
    // ==>       x = x /8 + x /16;
    // ==>      x/16    ==  x/8 /2 !!
    uint16_t temp = val[i] / 8;      // no floating point math needed, and compiler can optimize division of powers of 2 !!
    val[i] = temp + temp / 2;

    // stmp[ i * 2] = val[i] / 256;
    // stmp[ i * 2 + 1] = val[i] % 256;
    // you split the data in 2 bytes here
    // furthermore the index of smtp is "a lot of math" which can be simplified as we just need the next element.

    stmp[idx++] = val[i] / 256;        // power of 2 will be optimized.
    stmp[idx++] = val[i] & 0xFF;    // just do bit masking instead of % division - compiler might do that too?

    // send data:  id = 0x70, standard frame, data len = 32, stmp: data buf
    CAN0.sendMsgBuf(0x70, 0, 32, stmp);
  }
}

Get the idea?


however filling the array may not be the time consuming part....

RobTillaart commented 3 years ago

last note - you still could try the ADS1015 (which is faster) and the MCP3208 if you need extra speed (and less resolution)

GIven that the data is roughly divided by 4 when sending over the CAN bus a 12 bit conversion could be good enough..