Sensing color with the ADJD-S371 + Arduino

About 2 years ago I picked up a ADJD-S371 color sensor from Sparkfun to work with my arduino. I spent a few days getting it to work, but finally got it going pretty well. I still get a few emails here and there asking for help with it. So I figured I would start bildr’s tutorial posts off with this one.

Before we begin, I want to say that I could not have done this without the awesome help of Marcus over at Interactive Matter – Almost all of this code is of his hand, and he has gracefully released the code to bildr under the MIT license for users to build off of.

Im a huge fan of getting to the punch, it is what bildr is all about. So if you just want something to start with… here you go.

Sensing color with the ADJD-S371 + Arduino

//Configure gain here
//Higher numbers = less sencitive
// 0x00 through 0x0f
int redGain = 0x03;
int greenGain = 0x0;
int blueGain = 0x0f;
int clearGain = 0x01;

//RGB LED pins
//Digital PWM pins
int redPin = 9;
int greenPin = 10;
int bluePin = 11;

//Include the I2C Arduino library
#include <Wire.h>

//7 bit I2C address of this sensor
#define I2C_ADDRESS 0x74

#define REG_CAP_RED		0x06
#define REG_CAP_GREEN	 0x07
#define REG_CAP_BLUE	 0x08
#define REG_CAP_CLEAR	 0x09

#define REG_INT_RED_LO	0x0A
#define REG_INT_RED_HI	0x0B
#define REG_INT_GREEN_LO 0x0C
#define REG_INT_GREEN_HI 0x0D
#define REG_INT_BLUE_LO	0x0E
#define REG_INT_BLUE_HI	0x0F
#define REG_INT_CLEAR_LO 0x10
#define REG_INT_CLEAR_HI 0x11

#define REG_DATA_RED_LO	0x40
#define REG_DATA_RED_HI	0x41
#define REG_DATA_GREEN_LO 0x42
#define REG_DATA_GREEN_HI 0x43
#define REG_DATA_BLUE_LO 0x44
#define REG_DATA_BLUE_HI 0x45
#define REG_DATA_CLEAR_LO 0x46
#define REG_DATA_CLEAR_HI 0x47

float redFactor=1;
float blueFactor=1;
float greenFactor=1;

//initial darkLevel;
int calibrationDarkness = 0;
byte calibrationRed = 5;
byte calibrationGreen = 5;
byte calibrationBlue = 5;

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

	// sensor gain setting (Avago app note 5330)
	// CAPs are 4bit (higher value will result in lower output)
	set_register(REG_CAP_RED, redGain);
	set_register(REG_CAP_GREEN, greenGain);
	set_register(REG_CAP_BLUE, blueGain);
	set_register(REG_CAP_CLEAR, clearGain);

	int ledGain = getColorGain();

	set_gain(REG_INT_RED_LO,ledGain);
	set_gain(REG_INT_GREEN_LO,ledGain);
	set_gain(REG_INT_BLUE_LO,ledGain);

	performMeasurement();

	int red=get_readout(REG_DATA_RED_LO);
	int green=get_readout(REG_DATA_GREEN_LO);
	int blue=get_readout(REG_DATA_BLUE_LO);

	int m=2000; //bigger anyway
	m=min(m,red);
	m=min(m,green);
	m=min(m,blue);

	//Serial.print("m - ");
	//Serial.println(m);

	redFactor=((float)m*255.0)/(1000*(float)red);
	greenFactor=((float)m*255.0)/(1000*(float)green);
	blueFactor=((float)m*255.0)/(1000*(float)blue);

}

void loop() { 

	int clearGain = getClearGain();
	set_gain(REG_INT_CLEAR_LO,clearGain);
	int colorGain = getColorGain();
	set_gain(REG_INT_RED_LO,colorGain);
	set_gain(REG_INT_GREEN_LO,colorGain);
	set_gain(REG_INT_BLUE_LO,colorGain);

	//reset the RGB (and clear) values
	int cc = 0;
	int red=0;
	int green=0;
	int blue=0;

	// Take 4 samples, and add them together.
	for (int i=0; i<4 ;i ++) {
		performMeasurement();
		cc +=get_readout(REG_DATA_CLEAR_LO);
		red +=get_readout(REG_DATA_RED_LO);
		green +=get_readout(REG_DATA_GREEN_LO);
		blue +=get_readout(REG_DATA_BLUE_LO);
	}

	//now, divide the totals for each by 4 to get their average.
	cc/=4;
	red /=4;
	green /=4;
	blue /=4;

	//take the values mesured from above, and multiply them with the factors to
	//find out what value should be sent to the external RGB LED to reproduce this color
	float redValue = (float)red*redFactor;
	float greenValue = (float)green*greenFactor;
	float blueValue = (float)blue*blueFactor;

	Serial.print("red: ");
	Serial.print(redValue);

	Serial.print("green: ");
	Serial.print(greenValue);

	Serial.print("blue: ");
	Serial.print(blueValue);

	//send to LED
	analogWrite(redPin, map(redValue, 0, 1024, 0, 255));
	analogWrite(greenPin, map(greenValue, 0, 1024, 0, 255));
	analogWrite(bluePin, map(blueValue, 0, 1024, 0, 255));	

	//hold it for one second
	delay(1000); 
}

int getClearGain() {
	int gainFound = 0;
	int upperBox=4096;
	int lowerBox = 0;
	int half;

	while (!gainFound) {
		half = ((upperBox-lowerBox)/2)+lowerBox;

		if (half == lowerBox) { //no further halfing possbile
			break; //no further halfing possbile
		} 
		else {
			set_gain(REG_INT_CLEAR_LO,half);
			performMeasurement();
			int halfValue = get_readout(REG_DATA_CLEAR_LO);

			if (halfValue > 1000) {
				upperBox=half;
			} 
			else if (halfValue<1000) {
				lowerBox = half;
			} 
			else {
				break; //no further halfing possbile
			}
		}
	}
	return half;
}

int getColorGain() {
	int gainFound = 0;
	int upperBox=4096;
	int lowerBox = 0;
	int half;
	while (!gainFound) {
		half = ((upperBox-lowerBox)/2)+lowerBox;

		if (half==lowerBox) { //no further halfing possbile
			break; // gain found
		} 
		else {

			set_gain(REG_INT_RED_LO,half);
			set_gain(REG_INT_GREEN_LO,half);
			set_gain(REG_INT_BLUE_LO,half);
			performMeasurement();
			int halfValue = 0;

			halfValue=max(halfValue,get_readout(REG_DATA_RED_LO));
			halfValue=max(halfValue,get_readout(REG_DATA_GREEN_LO));
			halfValue=max(halfValue,get_readout(REG_DATA_BLUE_LO));

			if (halfValue>1000) {
				upperBox=half;

			} 
			else if (halfValue<1000) {
				lowerBox=half;

			} 
			else {
				break; // gain found
			}
		}
	}
	return half;
}

void performMeasurement() {
	set_register(0x00,0x01); // start sensing

	while(read_register(0x00) != 0) {
		// waiting for a result
	}
}

int get_readout(int readRegister) {
	return read_register(readRegister) + (read_register(readRegister+1)<<8);
}

void set_gain(int gainRegister, int gain) {
	if (gain <4096) {
		uint8_t hi = gain >> 8;
		uint8_t lo = gain;

		set_register(gainRegister, lo);
		set_register(gainRegister+1, hi);
	}
}

void set_register(unsigned char r, unsigned char v){

	Wire.beginTransmission(I2C_ADDRESS);
	Wire.send(r);
	Wire.send(v);
	Wire.endTransmission();
}

unsigned char read_register(unsigned char r){

	unsigned char v;
	Wire.beginTransmission(I2C_ADDRESS);
	Wire.send(r); // register to read
	Wire.endTransmission();

	Wire.requestFrom(I2C_ADDRESS, 1); // read a byte
	while(!Wire.available()) {
		// waiting
	}
	v = Wire.receive();
	return v;
}

If you are still with us and want to know more about getting the most from this guy, cool!

A few things you should know about the sensor before we dive in too deep: I have never been able to get perfect color sampling from this guy without limiting the colors it would detect to just 6, and accurately reproducing color on an LED is not as simple as one would hope. The color reading from the sensor could be spot on, but the reproduced color on the LED may be way off. Also, the sensor has gain settings for RGB, and white, these will need to be tweaked to get good results.

As you may know, the ADJD-S371 is an I2C component so you can’t just read simple serial or analog values from it. Our arduino code uses the Wire.h library to communicate with it, making it a bit easier, but it is still hard to do, and I’m not going to go into detail of how any why it works here.

Sensing color with the ADJD-S371 + Arduino

Mostly what you need to know about hooking up an I2C device to your arduino is that I2C is a 2-wire serial connection, SDA (Data) and SCL (clock) – On your arduino (everything but the mega) SDA is on analog input pin 4, and SCL is on analog pin 5. On an arduino mega, SDA is digital 20, and SCL is digital 21.

The ADJD-S371 has 4 sensors on it to detect Red, Green, Blue, and Clear. It reports back an individual reading from each sensor. The white, or clear, sensor is mainly for sensing brightness, but because the LED we will be using to reproduce the color is only an RGB LED, we are just going to disregard the white value.

The ADJD-S371 is designed to sense reflected light using its onboard LED, but I have found that by turning the onboard LED off, it works even better for sensing projected light (like from your monitor or projector). If you are interested in using the sensor for sensing reflected colors, you want the sensor to be about 1-2mm from the subject so the onboard LED can bounce off the material and back to the sensor.

It is not clear in any of the documentation, but I believe from looking at the schematic, and reading forums, that the onboard LED should be powered by 5v, not 3.3v. If this is wrong, please let me know, but the LED is too dim to work when powered at 3.3v.

 

For more detail: Sensing color with the ADJD-S371 + Arduino


About The Author

Ibrar Ayyub

I am an experienced technical writer holding a Master's degree in computer science from BZU Multan, Pakistan University. With a background spanning various industries, particularly in home automation and engineering, I have honed my skills in crafting clear and concise content. Proficient in leveraging infographics and diagrams, I strive to simplify complex concepts for readers. My strength lies in thorough research and presenting information in a structured and logical format.

Follow Us:
LinkedinTwitter

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top