Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ADS1X15::setWireClock() and high-speed mode #39

Closed
MaffooClock opened this issue Mar 9, 2022 · 11 comments · Fixed by #41
Closed

ADS1X15::setWireClock() and high-speed mode #39

MaffooClock opened this issue Mar 9, 2022 · 11 comments · Fixed by #41
Assignees
Labels
question Further information is requested

Comments

@MaffooClock
Copy link

Issue #22 added this handy method for setting the I2C clock, however, it might be incomplete.

Section 9.5.1.3 (page 23) of the datasheet says that while the ADS111x supports all three speed modes, high-speed mode (>400kHz) must be activated:

To activate high-speed mode, send a special address byte of 00001xxx following the START condition, where xxx are bits unique to the Hs-capable master. This byte is called the Hs master code, and is different from normal address bytes; the eighth bit does not indicate read/write status. The ADS111x do not acknowledge this byte; the I2C specification prohibits acknowledgment of the Hs master code. Upon receiving a master code, the ADS111x switch on Hs mode filters, and communicate at up to 3.4 MHz. The ADS111x switch out of Hs mode with the next STOP condition.

It'd be pretty slick to see the ADS1X15::setWireClock() method perform this activation.

@MaffooClock
Copy link
Author

Actually, I might be stupid.

Maybe this is part of the I2C spec, and so the Wire library (or platform or whatever) handles this? I briefly skimmed through those libraries to confirm, and consulted the I2C spec, but didn't find what I was looking for.

I tested higher clock speeds with this library and was able to reach 3MHz -- isConnected() returned true at 3.4MHz, but all data was 0.

So I guess I was wrong about this library needing to perform the activation.

@RobTillaart
Copy link
Owner

Thanks for this issue.

Never used this high speed as 400KHz works stable for almost all devices.
Still indeed interesting for those boards that can handle 3.4MHz. (an UNO can do ~800KHz under ideal conditions)
Never seen the needed commands in any ADS library or sketch.

What board are you using?

code wise

(for future reference)

void ADS1X15::setWireClock(uint32_t clockSpeed)
{
  _clockSpeed = clockSpeed;
  _wire->setClock(_clockSpeed);

  // Section 9.5.1.3 (page 23) of the datasheet - https://www.ti.com/lit/ds/symlink/ads1115.pdf
  if (clockSpeed > 400)
  {
   // insert magic here
  }
}

@MaffooClock
Copy link
Author

MaffooClock commented Mar 9, 2022

Never used this high speed as 400KHz works stable for almost all devices.

I would agree.

However, I'm using an ADS1114 to read a 60Hz sine wave -- at 860S/s, the best I can do is about 14 samples per full cycle of the wave. (A faster ADC would be smarter, but I've got a $8k reel of these, so I'm kinda stuck with it. But I digress.) Thus, I'm trying to put the squeeze on every possible optimization, and in this case, jumping from 400kHz to 3MHz has really improved accuracy.

What board are you using?

It's a MicroMod RP2040 (SparkFun's version of the Raspberry Pi Pico) mounted to a custom PCB which has multiple ADS1114s.

@RobTillaart
Copy link
Owner

RobTillaart commented Mar 9, 2022

If you have a whole real of those guys why not put 2 side by side?
if you read them alternating you might combine the samples ?
using 2 different interrupt pins to get timing per sample on micros level?

never tried but there is no law of nature that forbids this trick ...

might even work for more than 2 ...

@MaffooClock
Copy link
Author

That is a clever idea. 🤔

@RobTillaart
Copy link
Owner

I'm interested in a minimal code version as it would make a great example.

@RobTillaart RobTillaart reopened this Mar 9, 2022
@RobTillaart
Copy link
Owner

RobTillaart commented Mar 9, 2022

@MaffooClock
A quick sketch for 4x ADS1114 (stripped / converted example)
It is blocking but it should show the idea is working.

//
//    FILE: ADS_1114_four.ino
//  AUTHOR: Rob.Tillaart
// PURPOSE: demo reading four ADS1114 modules in parallel
//     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"


ADS1114 ADS[4];
uint16_t val[4];

uint32_t last = 0, now = 0;


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

  for (uint8_t i = 0; i < 4; i++)
  {
    uint8_t address = 0x48 + i;
    ADS[i] = ADS1114(address);

    Serial.print(address, HEX);
    Serial.print("  ");
    Serial.println(ADS[i].begin() ? "connected" : "not connected");

    ADS[i].setDataRate(4);  // 7 is fastest, but more noise
  }
  ADS_request_all();
}


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

  // we have all values
  ADS_print_all();

  delay(1000);      // wait a second. <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<  comment this linw
  ADS_request_all();
}


void ADS_request_all()
{
  //  Serial.println(__FUNCTION__);
  for (int i = 0; i < 4; i++)
  {
    if (ADS[i].isConnected()) ADS[i].requestADC(0);
    delayMicroseconds(200);  // get them evenly spaced in time ...
  }
}


bool ADS_read_all()
{
  for (int i = 0; i < 4; i++)
  {
    if (ADS[i].isConnected() && ADS[i].isBusy()) return true;
  }
  // Serial.print("IDX:\t");
  // Serial.println(idx);
  for (int i = 0; i < 4; i++)
  {
    if (ADS[i].isConnected())
    {
      val[i] = ADS[i].getValue();
    }
  }
  ADS_request_all();
  return false;
}


void ADS_print_all()
{
  // Serial.println(__FUNCTION__);
  // TIMESTAMP
  now = millis();
  Serial.print(now - last);
  last = now;
  Serial.println();

  // PRINT ALL VALUES
  for (int i = 0; i < 4; i++)
  {
    Serial.print(val[i]);
    Serial.print("\t");
  }
  Serial.println();
}


// -- END OF FILE --

@RobTillaart RobTillaart self-assigned this Mar 9, 2022
@RobTillaart RobTillaart added the question Further information is requested label Mar 9, 2022
@RobTillaart
Copy link
Owner

@MaffooClock

Another quick one - (have no hardware to test real thing ) for two ADS1114 in continuous mode.

  • assumes UNO with 2 hardware interrupt pins.
    give it a try.
//
//    FILE: ADS_1114_two_continuous.ino
//  AUTHOR: Rob.Tillaart
// PURPOSE: demo reading four ADS1114 modules in parallel
//     URL: https://github.com/RobTillaart/ADS1X15


#include "ADS1X15.h"


ADS1114 ADS_1(0X48);
ADS1114 ADS_2(0X49);


//  four interrupt flags
volatile bool RDY_1 = false;
volatile bool RDY_2 = false;

int16_t val_1 = 0;
int16_t val_2 = 0;


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


  // SETUP FIRST ADS1115
  ADS_1.begin();
  ADS_1.setGain(0);         // 6.144 volt
  ADS_1.setDataRate(7);

  // SET ALERT RDY PIN
  ADS_1.setComparatorThresholdHigh(0x8000);
  ADS_1.setComparatorThresholdLow(0x0000);
  ADS_1.setComparatorQueConvert(0);

  // SET INTERRUPT HANDLER TO CATCH CONVERSION READY
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), adsReady_1, RISING);

  ADS_1.setMode(0);         // continuous mode
  ADS_1.readADC(0);         // trigger first read

  // SETUP SECOND ADS1115
  ADS_2.begin();
  ADS_2.setGain(0);         // 6.144 volt
  ADS_2.setDataRate(7);

  // SET ALERT RDY PIN
  ADS_2.setComparatorThresholdHigh(0x8000);
  ADS_2.setComparatorThresholdLow(0x0000);
  ADS_2.setComparatorQueConvert(0);

  // SET INTERRUPT HANDLER TO CATCH CONVERSION READY
  pinMode(3, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(3), adsReady_2, RISING);

  ADS_2.setMode(0);         // continuous mode
  ADS_2.readADC(0);         // trigger first read
}


void loop()
{
  if (handleConversion() == true)
  {
    Serial.print('\t');
    Serial.print(val_1);
    Serial.print('\t');
    Serial.print(val_2);
    Serial.println();
  }
}

// catch interrupt and set flag device 1
void adsReady_1()
{
  RDY_1 = true;
}

// catch interrupt and set flag device 1
void adsReady_2()
{
  RDY_2 = true;
}

// handle conversions that are ready
bool handleConversion()
{
  bool rv = false;
  if (RDY_1)
  {
    // save the last value
    val_1 = ADS_1.getValue();
    ADS_1.readADC(0);
    RDY_1 = false;
    rv = true;
  }
  if (RDY_2)
  {
    // save the last value
    val_2 = ADS_2.getValue();
    ADS_2.readADC(0);
    RDY_2 = false;
    rv = true;
  }
  return rv;
}


// -- END OF FILE --

@RobTillaart
Copy link
Owner

@MaffooClock
Made any progress?
Can you confirm the examples work?

@MaffooClock
Copy link
Author

MaffooClock commented Mar 15, 2022

My apologies for going dark... got busy, I'm sure you know how that is :)

Actually, I might be stupid.

As I mentioned in my second reply, the "magic" required by the ADS111x to switch into high-speed mode might actually be an I2C thing and not a ADX111x thing? And thus the Wire library (or hardware library) handles that? I only took a few moments to try to confirm, but didn't find anything.

With that thought, I Just Tried It™ -- I set the clock to 3.4MHz, and I was able to communicate with the ADS1114 using your library without any additional steps.


I guess now you're addressing my side quest of getting the maximum possible speed, and had a clever suggestion to use tandem ADX111x's. For this project, I've already got a custom PCB for my Teensy and RP2040 -based device (dual MCUs), with integrated ADS1114's. I don't really have a way to test the multi-ADC idea with what I'm working on. Plus, I don't plan on re-spinning these PCBs to accommodate this design.

So, no sir, I haven't tested your ideas.

However, I can say that with your library I am able to sample a 60Hz sine wave accurately enough through multiple ADS1114s to calculate phase angle between voltage and current (resistive vs capacitive vs inductive loads), which allows calculation of VA (apparent power) and watts (true power), and thus power factor. The ideal way to do this would be to use an ADC that can take more than ~14 samples during on 60Hz cycle, but from my tests, this is plenty accurate for my project.

Screenshot 2022-02-22 093210

I appreciate your interest and efforts -- in almost every issue in you projects, you're always engaged and helpful, and that's awesome.

@RobTillaart
Copy link
Owner

I set the clock to 3.4MHz, and I was able to communicate...

Good to know, tested with which processor?


No problem that you are busy (quite familiar with that :) and cannot test the examples
As the examples are pretty straight forward derivates of tested code I expect them to work and hey, it are examples.
So I will merge the two ADS1114 examples + fix of other open issue and release a new version.
That will close this issue, however if questions arise you can always reopen it .
Feel free to report progress if you want.

Nice board BTW

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants