I2C data is too slow

Hello everyone,

I've read lots of threads on here, but I've never posted a question myself. I've always been able to find the answer to something from someone else's question. Or at least it would point me in the right direction. But this time I'm really stuck.

I have 7 photo-resistors in an array. And I would like to read their values to estimate the position of objects obstructing light falling on them.

I found using the analog inputs on the microcontrollers themselves it was too noisy. So I decided to use off board ADCs instead (two ADS1115s). The readings are super stable, so I'm very impressed with that. The problem is the speed of the I2C data. I'd like to run at 60 frames per second (which is a very smooth gaming frame rate). That's 16 bits x 7 sensors x 60 per second (6,720 bits/s). This is well below the I2C specification. But I'm getting about 15 measurements per second at best. Sometimes less that this, depending on the setup (please see below).

I was working with a Nano 33 IoT, but then wondered if the speed of the device would make a difference, so I tried it on an ESP32. Virtually no difference. I know there are pull up resistors on the device, but I decided to add my own (two 5K resistors). No difference what so ever. I know the recommendation is 3K3 with 3.3V, but if 5K didn't do anything I can't see 3K3 changing the world.

I then started to suspect the Adafruit_ADS1X15.h library, So I tried ADS1115_WE by Wolfgang Ewold instead. It took more code, but produced similar results.

I then started to suspect the Wire library that they are both dependant on. I read on a different post that the Wire library worked fine with the ATmega processors. So I dug out an old Uno and tried it on that. No difference.

I even considered if I should start modifying libraries, but that seems a bit drastic. Surely these are standards that thousands of builders are using all the time?
Any suggestions would be gratefully received. Especially if I'm doing something silly and I could solve it easily.
Thanks.

Measurement_firmware_code.ino (1.25 KB)

How long does an external ADC take to get a reading when you use readADC_SingleEnded? Perhaps that's where the time is going.

The ADC on the Arduino can be used in asynchronous mode - there's library that supports it. It might be worth looking at the library you're using to see whether you could do the same here. Right now you're reading each ADC in series.

Thanks WildBill, that's definitely where all the times going. I added a millis() either side of readADC_SingleEnded() and each sample is taking about 48 milliseconds to come back. But I suppose the question is, is it the ADC or the I2C creating this lag?

The ADS1115 is supposed to be able to make 860 samples per second. Even at half this they would achieve my desired 60 fps (420 samples/sec). It's barely making 105 samples/sec.

Because of the data sheet on the ADC, I'm not sure it's actually the speed of the ADC, but rather the data transfer by the I2C. I supposed I'm going to have to think about how I'm going to measure it to find out.

As wildbill expected, the readADC_SingleEnded() starts a conversion, then waits until that is finished and then it returns that value.
The waiting is here: Adafruit_ADS1X15/Adafruit_ADS1X15.cpp at master · adafruit/Adafruit_ADS1X15 · GitHub.

You need a asynchronous library. But wait, that is not the problem ::slight_smile:

You need a ADC with SPI interface. But wait, that is not the problem either :o

The problem is that you did something that causes noise for the internal ADC. You should fix that, instead of fixing one problem by creating a few other problems.

What is a photo-resistors ? Is that a LDR ? They are slow in certain low-light situations.
Can you tell how 7 LDRs fit in a matrix ?

The Nano 33 IoT and the ESP32 use 3.3V processors. You should feed the resistor with LDR with the same 3.3V that powers the processor.
The analog inputs of the Arduino boards are not bad, they are actually very good.
The analog input of the ESP32 is however not good, but still good enough for a LDR.
Can you make a schematic or a photo of a drawing or a photo how you did connect the LDRs to the Arduino board ? so we can check the wiring.

Koepel:
You need a asynchronous library. But wait, that is not the problem ::slight_smile:

Actually I thought the asynchronous suggest was a good one. However, you're right that's not the problem, as the specification of both the ADCs and I2C should be more than capable of delivering my requirements.

Koepel:
You need a ADC with SPI interface. But wait, that is not the problem either :o

I don't think anyone mentioned SPI. Especially as I2C is the standard protocol for interfacing with sensors like ADCs. Also there are a few disadvantages of the SPI protocol for this case. For instance is there are 4 connections as opposed to 2. Also there isn't the ability to address different devices. There are ways to get round this will additional connections, but it's not ideal. Just dumbing I2C because it's not working to specification and opting for SPI instead seems a bit rash.

Koepel:
The problem is that you did something that causes noise for the internal ADC. You should fix that, instead of fixing one problem by creating a few other problems.

I did fix the noise issue. I opted for a much lower noise stand alone ADC, which offers a better resolution of 16 bits as well. I think it's a nice solution.

Koepel:
What is a photo-resistors ? Is that a LDR ? They are slow in certain low-light situations.
Can you tell how 7 LDRs fit in a matrix ?

I'm using standard GI5528s. Yes a photo-resistor is a LDR. I used the word matrix because I though that would be easier for people to understand. Below is an example of a shape:


Koepel:
The Nano 33 IoT and the ESP32 use 3.3V processors. You should feed the resistor with LDR with the same 3.3V that powers the processor.

I am.

Koepel:
The analog inputs of the Arduino boards are not bad, they are actually very good.

I agree, I think for many applications they're suitable. However depending on your requirements I believe specialised ADCs have their place.

Koepel:
The analog input of the ESP32 is however not good, but still good enough for a LDR.

I would agree again, in fact I would go as far to say the analog inputs on the ESP32 are crap. It's one of its biggest down falls, probably due to the fast read speed. They would be better off slowing the ADC measurement down so they provided something usable. Rather than making the user have to average a load of rubbish measurements to get something half decent.

However, I disagree that it's fine for all LDR measurements. it depends on what you are using the LDR signal to do. Sensing small variation in luminance just gets lost in the noise.

Koepel:
Can you make a schematic or a photo of a drawing or a photo how you did connect the LDRs to the Arduino board ? so we can check the wiring.

Thank you for the offer. However, as that part of the circuit has been working in different prototypes, I think I'm good.

Now I understand the "matrix" :stuck_out_tongue:

GL5528 : datasheet at Sparkfun.
That is a normal LDR, often used with 10k resistor.
If your LDR should be more sensitive in the dark, then you can use a higher value for the resistor.

Sometimes I extend the range of a LDR by using two digital outputs. One with 1k to the LDR and one with 100k to the LDR. I power them one by one and average the result. That improves the range.
Then I use the average of multiple samples and with a good balance between integers and float, I can get more resolution than the 10 bits of a Arduino Uno (not more accuracy, but I use the noise to get more bits).
That way I have so much more resolution then I need with a LDR.

I just can not image how something as inaccurate as a LDR is better with a 16-bit ADC than with a 12-bit ADC :confused:
Your working range should be somewhere in the middle for maximum precision.
Can you show your sketch ? or at least the part that reads the LDR ?
With the average of only 5 samples, there is a good improvement. If 5 is better, then I advice to use 20.

I really would like to see your circuit or a schematic. There could be devices in the ground path that disturb the sensors. I also want to see for myself that you power the resistor + LDR with the right 3.3V, not just any 3.3V.
The ADS1115 uses a fixed voltage as reference. The Arduino uses the VCC of the processor. The ADS1115 measures a voltage and the Arduino measures a ratio relative to the VCC. That makes a difference how the resistor + LDR should be connected.

It sounds like you're a lot more sophisticated with your LDR circuit than me. you've got some good ideas in there. I've just wired each LDR across Vcc and ground with a 12K resistor to act as a simple voltage divider. I chose the resistor value based on the brightness of the light source I'm using, so that the values are as close to the centre of the measurement range. As you know this should offer the best sensitivity.

I'm not really concerned about the accuracy of the measurements. As you will see from the serial port image each sensor is sitting at a different number (18736... 20720, 16800 ...etc..). But what's really impressive is when it's subjected to unchanging light, the sensors sit at that number almost unchanging for reading after reading. With ADCs onboard microcontrollers the last few digits are always flickering around, and that's fine for most applications. And using averaging is a good technique for reducing this. But these ADCs take single readings without any averaging at 16 bits and the numbers are rock solid in unchanging light. However the slightest change in luminance and they show it straight away. I know LDRs have a delay, but that's usually the time delay measured from total darkness to full illumination. What I'm measuring are tiny changes in illuminance. And that's what I'm interested in. I want to detect the change of luminance, not the accuracy of it.

I'm hoping if I can get good clean data, I can use machine learning algorithms to determine position and gestures for inference.

I would really like to get a very smooth data stream at 60 fps. That's why I'm beginning to wonder if there is a problem with the data flow on the I2C bus. If anyone could make any suggestions of how I could solve this, I would be extremely grateful.
Thanks.

A Arduino Uno can get data from gyro+accelerator+magnetometer at 100Hz rate and do some calculations.
Your SAMD processor can do more. The 60 samples per second of 16-bits data with two ADS1115 and each with 4 channels, that should be no problem at all.

You can't use a library that starts the ADC and waits until the ADC is ready.
The ADS1115 can be set in some kind of continuous mode by starting a new conversion as soon as the old one has been read. Then issue an interrupt when the new conversion is ready and so on.

Have a look at this: ADS1X15/ADS_continuous_4_channel.ino at master · RobTillaart/ADS1X15 · GitHub.
The library might be still in development, but Rob Tillaart makes more good and interesting libraries.

Thanks Koepel, yes I've found the problem and it's with the Adafruit_ADS1X15.h library. For some reason they thought it was a good idea to add in additional delays into the code??? This slows the acquisition down for no reason what so ever, as far as I can see. Maybe someone else can explain its purpose?

I already tried Rob Tillarts library, but ran into it a similar issue.

However, I've found a library that doesn't have these delay: GitHub - terryjmyers/ADS1115-Lite: ADS1115 ADC arduino library.

I'm in the middle of testing it. And it seems to work nicely. I'll post the code when I've got it finalised.

After some pointers I've found a solution:

Don't use the Adafruit_ADS1X15.h library. It's got crazy delays in it that will slow your data transfer down to a snails pace.

Instead use: GitHub - terryjmyers/ADS1115-Lite: ADS1115 ADC arduino library.

I have posted my code showing the solution.

Measurement_firmware_code2.ino (2.67 KB)

In my opinion it waits in the same way as the Adafruit library. Maybe the conversion speed is set higher. I did not look that good at the library.

An idea popped up: What about interleaving both ADCs ?

ads1 select channel 0, trigger
ads2 select channel 0, trigger

read ads1, select channel 1, trigger
read ads2, select channel 1, trigger

read ads1, select channel 2, trigger
read ads2, select channel 2, trigger

Then I go a step further and use a millis-timer for each getConversion().

int16_t adc[2][4];
int channel = 0;

...

currentMillis = millis();
if( currentMillis - previousMillis >= 4)
{
  previousMillis = currentMillis;

  adc[0][channel] = ads1.getConversion();
  ads1.setMux( channel);
  ads1.triggerConversion();

  adc[1][channel] = ads2.getConversion();
  ads2.setMux( channel);
  ads2.triggerConversion();

  channel++;
  if( channel >= 4)
  {
    channel = 0;
  }
}

The speed of the I2C bus can be set to 400kHz with setClock(400000UL) after the Wire.begin().

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.