TCS34725 clearInterrupt() operation and color integration

I'm making a color sensor system to record what my project produces for color values. I'm using the interrupt capability and getRawData_noDelay(). Using the ADAFRUIT TCS34725. I do not want the TCS to block code execution.

Does the TCS integration stop on interrupt? Does it wait until the interrupt is cleared to begin the next color integration cycle?

Lets assume this order of events.

  1. Wait for Interrupt, Clear TCS Interrupt // old interrupt might be hanging around
  2. Wait for Interrupt, Clear TCS Interrupt // sync to TCS color processing intergration
  3. Present color
  4. Wait for Interrupt
  5. Get ColorResult with getRawData_noDelay()
  6. Present Next Color
  7. Clear TCS Interrupt
  8. Record ColorResult
  9. GOTO line 4

I can't make much sense out of your post. There does not appear to be an interrupt that signals the end of an integration cycle. There is one that informs that a user-set low or high intensity limit has been exceeded.

In any case, there is no need to use interrupts to check for integration done. While the processor is doing whatever else it needs to do, it can read the device status flag and decide whether to read the result of the latest integration.

Here is what the data sheet says:

Interrupts
The interrupt feature simplifies and improves system efficiency by eliminating the need to poll the sensor for
light intensity values outside of a user-defined range. While the interrupt function is always enabled and its
status is available in the status register (0x13), the output of the interrupt state can be enabled using the RGBC
interrupt enable (AIEN) field in the enable register (0x00).
Two 16-bit interrupt threshold registers allow the user to set limits below and above a desired light level. An
interrupt can be generated when the Clear data (CDATA) is less than the Clear interrupt low threshold (AILTx)
or is greater than the Clear interrupt high threshold (AIHTx).
It is important to note that the thresholds are evaluated in sequence, first the low threshold, then the high
threshold. As a result, if the low threshold is set above the high threshold, the high threshold is ignored and only
the low threshold is evaluated.
To further control when an interrupt occurs, the device provides a persistence filter. The persistence filter allows
the user to specify the number of consecutive out-of-range Clear occurrences before an interrupt is generated.
The persistence filter register (0x0C) allows the user to set the Clear persistence filter (APERS) value. See the
persistence filter register for details on the persistence filter value. Once the persistence filter generates an
interrupt, it will continue until a special function interrupt clear command is received (see command register).

I'm looking at this CODE

And trying to understand how best to use it. It appears to be a straight forward method to get raw data from the sensor in a predictable / repeatable fashion.

Do you know what the code lines below actually mean?

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

That line of code sets the persistence to zero.

My straightforward approach is to just read the status register. If the lowest bit is 0, do other stuff. If 1, get the data. No need to set up or worry about interrupts.

1 Like

Well, that is a lot easier.

I'm now looking at the TCS34725 SPEC and the Adafruit library code.

I'm quite confused with how to use status register bit 0. The status register is read only. Bit 0, AVAILD, indicates that the RGBC channels have completed an integration cycle. This bit starts out as 0, then sets, and never seems to go back to zero.

So how does one know when the next integration time is complete? There does not seem a way to detect another integration cycle has completed.

What am I missing.

What is the evidence for this? Post your code, using code tags.

Edit: I just looked at the Adafruit library (which I don't use) and see that it has delays built in. Bad idea. I'm away from my setup and won't be able to access it for a while, so you might look around for another library.

This code represents the basic issue. colorsensor.ino (2.0 KB)

Display Results:

Found sensor
1. TCS STATUS: 10000
waiting for status bit 0 to set
status bit 0 set: 2. TCS STATUS: 10001

waiting for status bit 0 to clear

I cannot figure out how the device would know to clear the AVALID bit and start a new integration cycle

Has the library set up the sensor for one shot or repeated cycles of integration?

As I posted, I won't be able to get to my setup for a while and can't test anything.

I'm reading the entire spec and will look at the library's initialization routine

The interrupt method you tried may be the only option, without modifying the Adafruit library. I wrote my own, much simpler code.

Did you see this post, where the interrupt option is successfully used? Adafruit customer service forums • View topic - TCS34725 latency

I was trying to figure out what was going on inside the library and device so to know how to properly use it. So with your insights and some investigation. It works like this.

The AVALID status sets and remains set once the first integration cycle completes.

The only indication that the device integration cycle has completed is the status register interrupt bit, AINT. So by enabling the device interrupt it becomes an indicator for integration completed.

It is important to note that the next integration cycle starts right after the last one has completed. It does not wait. The question then becomes what happens with the integration data. Is it held in a latch to be updated when the next cycle completes?

TCS34725.PDF
The upper register will read the correct value even if additional ADC
integration cycles end between the reading of the lower and upper registers.

The device seems to just chug along doing repeated integration cycles and updates the registers once complete. It seems prudent to capture the data as soon as the integration cycle completes and before the next one has a chance to.

So the ISR could be used or the device status AINT bit could be monitored.

Either way the interrupt appears to be the only way to know when an integration cycle has completed. Clearing interrupt has no effect on the integration cycle. It just chugs along.

The Adafruit library decided to add the delays equal to the integration time when reading the RGBC data.

Example Code polls the Status AINT bit, clears it, and displays the time between. Note the delay(400); in the polling loop. This does not change the integration time. This proves interrupts are based on integration cycles alone. Integration cannot be directly affected by the user code. Once started it runs continuously.
colorsensor.ino (2.7 KB)

results of code run

Found sensor
1. TCS STATUS: 10001
waiting for status bit 0 to set
2. TCS STATUS: 10001

waiting for status bit 0 to clear

print elapsed time between status register interrupt bit
Interrupt Set, elapsed ms: 0
Interrupt Set, elapsed ms: 613
Interrupt Set, elapsed ms: 616
Interrupt Set, elapsed ms: 617
Interrupt Set, elapsed ms: 616

Glad you got it working. Since the "interrupt output" is just a logic level, which according to your code is connected to pin 2, you could simply poll Arduino pin 2, rather than the AINT bit.

When I get back home I'll check out the code I wrote, because I don't remember having any of this difficulty.

I just wanted to note I've done a bit more looking at this and updated the previous conclusion/ solution. As it turns out AVALID never clears while integration cycles are running. Integration cycles cannot be affected by code once running.

My application presents a color, waits for integration to complete, records the results, and repeats.

This loop is to be done once per integration cycle. So my code synchronize to the interrupt bit, present the new color, waits for interrupt, present the next color, record the previous sensed color, and repeat. This will result in one valid color capture per integration cycle.

Clearing the interrupt after recording the color will take care of a loop that exceeds integration time. The data is recorded on an SD Card and displayed on the monitor. So it may be possible to take too long depending on the integration time selected.

I finally got around to checking what I wrote for the TCS34725, and find that AVALID works as advertised. Something must be wrong either with the Adafruit library, or your use of it.

Here is minimal code required to effectively use the TCS34725 as a light sensor. Interrupts are not implemented.

#include <Wire.h>

// minimal Arduino TCS3472(5) I2C code
// put together with help from the VERY COMPLETE TSL2591 library by Austria Microsystems
// "TSL2591MI.h" Yet Another Arduino ams TSL2591MI Lux Sensor
// https://bitbucket.org/christandlg/tsl2591mi/src/master/
// https://www.arduino.cc/reference/en/libraries/tsl2591mi/
// SJ Remington 3/2021, 7/2021

// some useful device-specific constants
#define TCS3472_ADDR 0x29
#define TCS3472_CMD 0b10000000
#define TCS3472_TRANSACTION_AUTOINC 0b00100000
#define TCS3472_MASK_AEN 0b00000010
#define TCS3472_MASK_PON 0b00000001
#define TCS3472_MASK_AVALID 0b00000001

// device-specific register numbers
#define TCS3472_REG_ENABLE 0x00
#define TCS3472_REG_ATIME  0x01
#define TCS3472_REG_WTIME  0x03
#define TCS3472_REG_CONFIG 0x0D
#define TCS3472_REG_CONTROL 0x0F
#define TCS3472_REG_ID 0x12  //value 0x44 for TCS34725
#define TCS3472_REG_STATUS 0x13
#define TCS3472_REG_CDATAL 0x14
#define TCS3472_REG_CDATAH 0x15
#define TCS3472_REG_RDATAL 0x16
#define TCS3472_REG_RDATAH 0x17
#define TCS3472_REG_GDATAL 0x18
#define TCS3472_REG_GDATAH 0x19
#define TCS3472_REG_BDATAL 0x1A
#define TCS3472_REG_BDATAH 0x1B

// read a register in the TCS3472
int readRegister(uint8_t reg) {  //int return value, because -1 is possible
  Wire.beginTransmission(TCS3472_ADDR);
  Wire.write(TCS3472_CMD | TCS3472_TRANSACTION_AUTOINC | reg);
  Wire.endTransmission(false);
  Wire.requestFrom(TCS3472_ADDR, 1);
  return Wire.read();
}

//write value to TCS3472 register. Special functions not implemented
void writeRegister(uint8_t reg, uint8_t value)
{
  Wire.beginTransmission(TCS3472_ADDR);
  Wire.write(TCS3472_CMD | TCS3472_TRANSACTION_AUTOINC | reg);
  Wire.write(value);
  Wire.endTransmission();
}

// reset to power on state
void TCS3472_reset(void) {
  writeRegister(TCS3472_REG_ENABLE, TCS3472_MASK_AEN | TCS3472_MASK_PON);
  writeRegister(TCS3472_REG_CONFIG, 0);  //WLONG off
  delay(3); //must wait > 2.4 ms before RGBC can be initiated
}
// reset to sleep state
void TCS3472_sleep(void) {
  writeRegister(TCS3472_REG_ENABLE, 0);
}
// set gain 0 to 3 (low, med, high, max), set integration time 1 to 255 cycles
void TCS3472_config(uint8_t gain, uint8_t time) {
  if (time == 0) time = 1;
  writeRegister(TCS3472_REG_ATIME, 256 - (int)time);
  writeRegister(TCS3472_REG_CONTROL, gain & 3); //1x, 4x, 16x, 60x
}

// get RGBC readings
void TCS3472_getData(uint16_t *r, uint16_t *g, uint16_t *b, uint16_t *c ) {
  uint8_t t;
  t = readRegister(TCS3472_REG_RDATAH);
  *r = (t << 8) |  readRegister(TCS3472_REG_RDATAL);
  t = readRegister(TCS3472_REG_GDATAH);
  *g = (t << 8) |  readRegister(TCS3472_REG_GDATAL);
  t = readRegister(TCS3472_REG_BDATAH);
  *b = (t << 8) |  readRegister(TCS3472_REG_BDATAL);
  t = readRegister(TCS3472_REG_CDATAH);
  *c = (t << 8) |  readRegister(TCS3472_REG_CDATAL);
}

// power on and turn on ambient light sensor
void TCS3472_enable() {
  writeRegister(TCS3472_REG_ENABLE, TCS3472_MASK_AEN | TCS3472_MASK_PON);
}

// register dump for debugging
void TCS3472_regDump() {
  for (int i = 0; i < 0x1C; i++) {
    Serial.print("reg "); Serial.print(i);  Serial.print(" "); Serial.println(readRegister(i), HEX);
  }
}

void setup() {
  Serial.begin(9600);

  //wait for serial connection to open (only necessary on some boards)
  while (!Serial);

  Wire.begin();
  uint8_t id = readRegister(TCS3472_REG_ID); // read ID register
  if (id != 0x44) {
    Serial.print("ID: ");
    Serial.print(id, HEX);
    Serial.println("? TCS3472 not found");
    while (1);
  }
  Serial.println("TCS3472 OK");
  TCS3472_reset();
  TCS3472_enable();  //use TCS3472_sleep() to disable and power down
  TCS3472_config(1, 255); //med gain, max (255*2.4) ms integration time
  TCS3472_sleep();
  delay(100);
}

void loop() {
  uint16_t r, g, b, c;
  unsigned int delayc=0;
  TCS3472_enable();

  //no need to wait, but here we check when AVALID become high and quantify the delay.
  while ((readRegister(TCS3472_REG_STATUS) & TCS3472_MASK_AVALID) == 0) {delayc++;}; //wait for data available

  TCS3472_getData(&r, &g, &b, &c);
  TCS3472_sleep();

  Serial.print("rgbc: ");
  Serial.print(r);
  Serial.print(", ");
  Serial.print(g);
  Serial.print(", ");
  Serial.print(b);
  Serial.print(", ");
  Serial.print(c);
  Serial.print(",");
  Serial.println(delayc);  //quantify wait time
}