[SOLVED] Reading from TCS34725 slows down subsequent functions

When reading from the sensor in a function, execution of the subsequent functions is delayed by the duration specified for integration time. However, to faithfully discern between pastel colour, the 700ms setting is necessary.

Is there a way to read from the sensor with the 700ms setting and not block the loop()?

On another note, should one use the "usually forbidden" for-loop in a doSomething() function instead of millis(), so that the blocking effect of a for-loop becomes an advantage in this case? Meaning that then a doSomething function no longer relies on loop() ticks?

Thanks for any hints!

#include <Wire.h>
#include "Adafruit_TCS34725.h"

/* Initialise with specific int time and gain values 2_4, 24, 50, 101, 154, 700 */
Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_700MS, TCS34725_GAIN_1X);

uint16_t r, g, b, c, colorTemp, lux;

void setup(void)
{
  Serial.begin(115200);
}

void loop()
{
  readSensor();
  doSomething(); // Next function(s) only called when sensor finished reading
}

void readSensor()
{
  tcs.getRawData(&r, &g, &b, &c);
  colorTemp = tcs.calculateColorTemperature_dn40(r, g, b, c);
  lux = tcs.calculateLux(r, g, b);
}

void doSomething()
{
  Serial.print("Color Temp: "); Serial.println(colorTemp, DEC);
}

Is there a way to read from the sensor with the 700ms setting and not block the loop()?

Yes, use the low-level calls to read the values and ensure yourself to wait at least the integration time until you read the next data:

  uint16_t c = tcs.read16(TCS34725_CDATAL);
  uint16_t r = tcs.read16(TCS34725_RDATAL);
  uint16_t g = tcs.read16(TCS34725_GDATAL);
  uint16_t b = tcs.read16(TCS34725_BDATAL);

Okay, but if I "manually wait" 700ms integration time, wouldn't that just be like waiting 700ms using the library as it is?

The thing is that, while waiting 700ms by whatever method until the reading is integrated sufficiently, nothing else can take place, no?

If doSomething() contains some millis() timed servo, linear actuator, etc. code, that would still be blocked for 700ms until the next step can take place?

One could deliberately use a "forbidden for-loop" instead of millis() in doSomething() to deliberately block everything else from interrupting what goes on inside doSometing()?

But a for-loop is not good practice and may not even work, if, for example, some other time-based code is neccessary, isn't it?

Okay, but if I "manually wait" 700ms integration time, wouldn't that just be like waiting 700ms using the library as it is?

I didn't tell you to wait doing nothing as delay() would do. You can do anything in that time, just ensure the next call to read the values is at least the integration time after the last one.

If doSomething() contains some millis() timed servo, linear actuator, etc. code, that would still be blocked for 700ms until the next step can take place?

Why?

One could deliberately use a "forbidden for-loop" instead of millis() in doSomething() to deliberately block everything else from interrupting what goes on inside doSometing()?

I have no clue what you write about. Why is a for-loop forbidden? As your doSomething() just prints to the Serial interface it may be a bad example.

Have you read the .getRawData() function in the library.

If so, you would see that is is just the .read16 commands shown by @pylon followed by an integration time delay().

As pylon says, to make the code non blocking just call the read16 commands on a millis() timer set for you integration time or longer.

It can be useful to know that your question is because of this question: Multiple Things With Delay() - Programming Questions - Arduino Forum.

Adafruit TCS34725 library: GitHub - adafruit/Adafruit_TCS34725: Arduino library driver for Adafruit's TCS34725 RGB Color Sensor Breakout

In such situations I look for the longest time that the sensors needs. I think it is 700ms. Then I create a millis() timer of a little more, 800ms or 1 second, and within the millis() timer I start the sensor the first time, and read the data next time. And so on.
I update the result to a global variable.

That works in most situations, but sometimes I have to add code to indicate that the value is recent and has no errors.

pylon:
I didn't tell you to wait doing nothing as delay() would do. You can do anything in that time, just ensure the next call to read the values is at least the integration time after the last one.

I see, so what you say is that I should simply read

uint16_t c = tcs.read16(TCS34725_CDATAL);
uint16_t r = tcs.read16(TCS34725_RDATAL);
uint16_t g = tcs.read16(TCS34725_GDATAL);

which is fast, and then run whatever else, and only after 700ms or more have passed come back to read these three registers again; thanks, that sounds practical!

pylon:
Why?

If I put whatever millis() timed code in doSomething(), it only executes every 700ms.

pylon:
I have no clue what you write about. Why is a for-loop forbidden? As your doSomething() just prints to the Serial interface it may be a bad example.

Yes, I only put a generic line of code in doSomething() to show that the funciton is called only every 700ms, once the sensor is done. Hence my quick-fix idea to use a for-loop in doSomething instead of millis() timed code (that, which is its purpose) immediately returns to loop(); a for-loop blocks execution in loop(), and thus the next call to readSensor(), which is why, as I saw in many forum posts, their use is discouraged and millis() timed code is encouraged.

cattledog:
Have you read the .getRawData() function in the library.

If so, you would see that is is just the .read16 commands shown by @pylon followed by an integration time delay().

As pylon says, to make the code non blocking just call the read16 commands on a millis() timer set for you integration time or longer.

Yeah, thanks; I will do what pylon suggests, calling it every 700ms or even more rarely, as I can't swap out the material samples to be read quickly anyway.

Koepel:
It can be useful to know that your question is because of this question: Multiple Things With Delay() - Programming Questions - Arduino Forum.

Adafruit TCS34725 library: GitHub - adafruit/Adafruit_TCS34725: Arduino library driver for Adafruit's TCS34725 RGB Color Sensor Breakout

In such situations I look for the longest time that the sensors needs. I think it is 700ms. Then I create a millis() timer of a little more, 800ms or 1 second, and within the millis() timer I start the sensor the first time, and read the data next time. And so on.
I update the result to a global variable.

That works in most situations, but sometimes I have to add code to indicate that the value is recent and has no errors.

Thanks, yes, that sounds rather straightforward to implement. I will go down that route. Since the samples are unwieldy, I won't swap them out for being scanned every 700ms anyway; so polling the sensor the pylon way in much longer intervals like you also do makes sense.

I just looked at the library and a no delay (I guess Adafruit refers to integration time) reading is explicitly mentioned.

A GitHub user apparently had the same problem using getRawData() like I did. So getRawData_noDelay() via an ISR seems a good way to go. I just tried it out with a millis() timed servo while the sensor reads at 700ms and it works fine.

#include <Wire.h>
#include <Servo.h>
#include <Adafruit_TCS34725.h>

Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_700MS, TCS34725_GAIN_1X);
const int interruptPin = 2;
volatile boolean state = false;

const byte timeServoInterval = 6;
int timeSimUserInterval; // Set "do something" interval programmatically
unsigned long timeNowServo = 0;
unsigned long timeNowUser = 0;

int angleCurrent;
int angleTarget;

Servo ovres;

void isr()
{
  state = true;
}

/* tcs.getRawData() does a delay(Integration_Time) after the sensor readout.
  We don't need to wait for the next integration cycle because we receive an interrupt when the integration cycle is complete*/
void getRawData_noDelay(uint16_t *r, uint16_t *g, uint16_t *b, uint16_t *c)
{
  *c = tcs.read16(TCS34725_CDATAL);
  *r = tcs.read16(TCS34725_RDATAL);
  *g = tcs.read16(TCS34725_GDATAL);
  *b = tcs.read16(TCS34725_BDATAL);
}

void setup()
{
  pinMode(interruptPin, INPUT_PULLUP); //TCS interrupt output is Active-LOW and Open-Drain
  attachInterrupt(digitalPinToInterrupt(interruptPin), isr, FALLING);

  Serial.begin(19200);
  
  tcs.begin();
  tcs.write8(TCS34725_PERS, TCS34725_PERS_NONE); // Set persistence filter to generate an interrupt for every RGB Cycle, regardless of the integration limits
  tcs.setInterrupt(true);

  randomSeed(analogRead(0));
  ovres.attach(3);
  ovres.write(0);
  angleCurrent = ovres.read(); // Seed with initial value to start
  angleTarget = random(0, 180); // Seed with initial value to start
}

void loop()
{
  readSensor();
  simulateUserInput(); // Values (°) randomly generated within a range
  rotateServo();
}

void readSensor()
{
  if (state)
  {
    uint16_t r, g, b, c;
    getRawData_noDelay(&r, &g, &b, &c);

    Serial.print("R: "); Serial.print(r, DEC); Serial.print(" ");
    Serial.print("G: "); Serial.print(g, DEC); Serial.print(" ");
    Serial.print("B: "); Serial.print(b, DEC); Serial.print(" ");
    Serial.print("C: "); Serial.print(c, DEC); Serial.print(" ");
    Serial.println(" ");

    tcs.clearInterrupt();
    state = false;
  }
}

void rotateServo()
{
  if (millis() - timeNowServo >= timeServoInterval) // Check if it is time to rotate
  {
    timeNowServo = millis(); // Record the current time
    if (angleCurrent != angleTarget) // Don't write to servo if target angle is reached
    {
      if (angleCurrent <= angleTarget)
      {
        angleCurrent ++;
        ovres.write(angleCurrent);
      }
      else
      {
        if (angleCurrent >= angleTarget)
        {
          angleCurrent --;
          ovres.write(angleCurrent);
        }
      }
    }
  }
}

void simulateUserInput() // Set target angle programmatically (later through user input)
{
  if (millis() - timeNowUser > timeSimUserInterval)
  {
    timeNowUser = millis();
    timeSimUserInterval = random(1000, 8000);
    angleTarget = random(0, 180);
  }
}