Skip to main content

Tinkering with ADJD-S371-Q999

http://interactive-matter.eu/blog/2008/08/17/tinkering-with-adjd-s371-q999/

Tinkering with ADJD-S371-Q999

August 17, 2008
The ADJD breakou board by Sparkfun
The ADJD breakout board by Sparkfun
The initial idea of “How was your day, darling” was to record the colors for a day. You know a morning is golden, evening is reddish, thunderstorm is greenish and so on. Measuring this with a simple RGB LED is a brave approach – but hard and cumbersome. So I ordered an ADJD-S371-Q999 color sensor breakout board from Sparkfun and played around with it.
Finding resource on this chip is very hard and getting it really working is much harder. Dealing with this complex topic is much more difficult than I thought. So I prepared some arduino code to get you started (after the break).

First of all: How to feed it? It takes voltages up to 3.6 volt. An USB Buarduino loves 5 volt. First I tried to take the 3V from the FTDI232R on the Boardino. Don’t try this at home. The 3.3V is go for 50mA but neither enough to power the whole Boardino, nor to power the color sensor or a RGB LED. The FTD232R got so hot that I thought I fried it – the Boardino and USB port acted quite weird. But it was just overheated.
The ADJD with Buardino on the breadboard
The ADJD with Buardino on the breadboard
So I grabbed my 3.3V breadboard (see above, just a breadboard with some batteries, regulated down to 3.3V), disabled the Boardnio power supply, hooked up the I2C connection, enabled TWI in my sketch – and here we go:
The TWI/I2C uses the following ports on the Arduino:
  • SCL – the serial clock – is connected to a5 (analog input 5)
  • SDA – the serial data line – is connected to a5 (analog input 4)
  • SLEEP was tied to GND
  • LED was connected to pin 2
  • XCLK – the external clock was left floating.
TWI can be enabled quite simple, just by including the header “wire.h”:
#include
Grab the data sheet, find some application note, read it and pretend to understand what you are doing. Fine. But how do I actually talk to that thing? Google! At the end I came up with a sketch in a thread on the Sparkfun Forums. It got everything to write. But reading was more or less random. Until I found out that the reading from the ADJD was like writing the adress of the register and waiting for the answer. Somewhere in the Internet I found a piece of code to use (if you know where, please leave a note in the comments – I would really like to give the correct credit. But I did not find it again.):
#include 

const int serialSpeed=9600;
int ledPin = 2;    // the light

//ADJD Settings
#define ADJD 0x74

#define CAP_RED 0x06
#define CAP_GREEN 0x07
#define CAP_BLUE 0x08
#define CAP_CLEAR 0x09

#define INT_RED_LO 0x0A
#define INT_RED_HI 0x0B
#define INT_GREEN_LO 0x0C
#define INT_GREEN_HI 0x0D
#define INT_BLUE_LO 0x0E
#define INT_BLUE_HI 0x0F
#define INT_CLEAR_LO 0x10
#define INT_CLEAR_HI 0x11

#define DATA_RED_LO 0x40
#define DATA_RED_HI 0x41
#define DATA_GREEN_LO 0x42
#define DATA_GREEN_HI 0x43
#define DATA_BLUE_LO 0x44
#define DATA_BLUE_HI 0x45
#define DATA_CLEAR_LO 0x46
#define DATA_CLEAR_HI 0x47

#define OFFSET_RED 0x48
#define OFFSET_GREEN 0x49
#define OFFSET_BLUE 0x4A
#define OFFSET_CLEAR 0x4B

void setup() {
  pinMode(ledPin, OUTPUT);  // declare the ledPin as an OUTPUT
  Serial.begin(serialSpeed);           // set up Serial library at 9600 bps
  Wire.begin();
  Serial.println("Sending Calibration data ... ");
  adjd_init();
  Serial.println("Calibration data sent. ");
}

void loop() {
  digitalWrite(ledPin,HIGH);
  read_register(DATA_RED_LO);
  digitalWrite(ledPin,LOW);

  delay(4000);
}

static void adjd_init()
{
  write_register(CAP_RED, 0x05);
  write_register(CAP_GREEN, 0x05);
  write_register(CAP_BLUE, 0x05);
  write_register(CAP_CLEAR, 0x05);

  write_register(INT_RED_LO, 0xC4);
  write_register(INT_RED_HI, 0x09);
  write_register(INT_GREEN_LO, 0xC4);
  write_register(INT_GREEN_HI, 0x09);
  write_register(INT_BLUE_LO, 0xC4);
  write_register(INT_BLUE_HI, 0x09);
  write_register(INT_CLEAR_LO, 0xC4);
  write_register(INT_CLEAR_HI, 0x09);
} 

void read_adjd_offset() {
}

static void write_register(uint8_t register_name, uint8_t register_value)
{
  Wire.beginTransmission(ADJD);
  Wire.send(register_name);
  Wire.send(register_value);
  Wire.endTransmission();
}

static uint8_t read_register(uint8_t register_name)
{
  Wire.beginTransmission(ADJD);
  Wire.send(register_name);
  Wire.endTransmission();
  Wire.requestFrom(ADJD,2);
  Serial.print("read:");
  while (Wire.available()<1) {
    Serial.print(".");
  }
  int result = Wire.receive();
  Serial.println(result);
  return 0;
}
The routine for reading the register did the trick:
static uint8_t read_register(uint8_t register_name)
{
  Wire.beginTransmission(ADJD);
  Wire.send(register_name);
  Wire.endTransmission();
  Wire.requestFrom(ADJD,2);
  Serial.print("read:");
  while (Wire.available()>1) {
    Serial.print(".");
  }
  int result = Wire.receive();
  Serial.println(result);
  return 0;
}
That was a promising beginning! But the sketch did miss one big feature of the ADJD: It did not adapt to different light levels. I had to come up with a very clever idea how to calibrate it. The calibration was done adapting the integration time of the sensors to the light level. First for the clear channel (it is allays a bit more sensitive than the color levels):
int getClearGain() {
  int gainFound = 0;
  int upperBox=4096;
  int lowerBox = 0;
  int half;
  while (!gainFound) {
    half = ((upperBox-lowerBox)/2)+lowerBox;
    //no further halfing possbile
    if (half==lowerBox) {
      gainFound=1;
    }
    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 {
        gainFound=1;
      }
    }
  }
  return half;
}
After this worked flawlessly the same trick was used to get the perfect color detection gain. According to the data sheet in both cases 1000 was used as target value (for the clear readout or the one of the colors).
int getColorGain() {
  int gainFound = 0;
  int upperBox=4096;
  int lowerBox = 0;
  int half;
  while (!gainFound) {
    half = ((upperBox-lowerBox)/2)+lowerBox;
    //no further halfing possbile
    if (half==lowerBox) {
      gainFound=1;
    }
    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 {
        gainFound=1;
      }
    }
  }
  return half;
}
Two small helper method were used to read or write the 16bit registers (and yes they could have been named better. The gain setting routine is this:
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);
  }
and the read routine is this:
int get_readout(int readRegister) {
  return read_register(readRegister) + (read_register(readRegister+1) << 8);
}
Now I was able to get perfect readouts from the chip. The next, quite complicated task was to calibrate the ADJD. The whole setup was again covered by a half table tennis ball. But somehow the internal LED of the ADJD was far to weak to illuminate table tennis ball. So I added a second (white) LED to get a bit more effect. The calibration was done adding capacitors from the measurement (anyone who read the datasheet knows what I mean): You can set the number of capacitors used during the measurement. Unfortunately the data sheet only tells that the more capacitor you use the lower readout you get. How the capacitors work or what they are remains unclear. Now matter. The sketch was just implemented that way:
  • The white LED is lighted
  • Each color is read and the capacitors are trimmed to get more or less the same readout from each color channel, this is repeated until the gap between the readouts do not get smaller (I know - I will come up with a better routine for this).
  • The capacitor values are stored as defaults.
The sketch tries to keep the values as low as possible - since the drop between the clear and the color channels is high enough - this gives at least a comparable readout (for example if you use the same integration time for the clear and color values:
void calibrateChip() {
  calibrationRed = 0;
  calibrationBlue = 0;
  calibrationGreen = 0;
  byte calibrated = 0;

  //neede to store detect better calibration
  int oldDiff = 5000;

  while (!calibrated) {
    //enabled the white light
    digitalWrite(ledPin, HIGH);
    // calibrate the sensor

    // sensor gain setting (Avago app note 5330)
    // CAPs are 4bit (higher value will result in lower output)
    set_register(REG_CAP_RED, calibrationRed);
    set_register(REG_CAP_GREEN, calibrationGreen);
    set_register(REG_CAP_BLUE, calibrationBlue);

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

    int maxRead = 0;
    int minRead = 4096;
    int red=0;
    int green=0;
    int blue=0;
    for (int i=0; i<4 ;i ++) {
      performMeasurement();
      red +=get_readout(REG_DATA_RED_LO);
      green +=get_readout(REG_DATA_GREEN_LO);
      blue +=get_readout(REG_DATA_BLUE_LO);
    }
    red /=4;
    green /=4;
    blue /=4;

    maxRead = max (maxRead, red);
    maxRead = max (maxRead, green);
    maxRead = max (maxRead, blue);

    minRead = min(minRead, red);
    minRead = min(minRead, green);
    minRead = min(minRead, blue);

    int diff = maxRead - minRead;

    if (oldDiff != diff) {
      if ((maxRead==red) &&; (calibrationRed<15)) {
        calibrationRed++;
      }
      else if ((maxRead == green) && (calibrationGreen<15)) {
        calibrationGreen++;
      }
      else if ((maxRead == blue) && (calibrationBlue<15)) {
        calibrationBlue++;
      }
    }
    else {
      calibrated = 1;
    }
    oldDiff=diff;

    int rCal = calibrationRed;
    int gCal = calibrationGreen;
    int bCal = calibrationBlue;

    Serial.print("calibration :");
    Serial.print(diff);
    Serial.print(" r=");
    Serial.print(rCal);
    Serial.print("/");
    Serial.print(red);
    Serial.print(", g=");
    Serial.print(gCal);
    Serial.print("/");
    Serial.print(green);
    Serial.print(" b=");
    Serial.print(bCal);
    Serial.print("/");
    Serial.println(blue);
  }

  digitalWrite(ledPin, LOW);
}
The next step was to calibrate the RGB LED against the ADJD. If you send the same courrent to each color of the RGB LED you get some red result. Since the red LED normally is the most efficient. The least efficient LED is normally the blue one. So I developed a way to calibrate the different LED colors:
These routines use floating point variables, tis might not be the most efficient way - but the math was complex enough. In the end the calibration is calculated the following way:
float redFactor=1;
float blueFactor=1;
float greenFactor=1;

void calibrateRGB() {
  digitalWrite(redPin,HIGH);
  digitalWrite(bluePin,HIGH);
  digitalWrite(greenPin,HIGH);
  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);
  digitalWrite(redPin,0);
  digitalWrite(bluePin,0);
  digitalWrite(greenPin,0);
  int m=2000; //bigger anyway
  m=min(m,red);
  m=min(m,green);
  m=min(m,blue);
  redFactor=((float)m*255.0)/(1000*(float)red);
  greenFactor=((float)m*255.0)/(1000*(float)green);
  blueFactor=((float)m*255.0)/(1000*(float)blue);
}
And the replay of the 10bit readouts goes like this:
  float rv = (float)red*redFactor;
  float gv = (float)green*greenFactor;
  float bv = (float)blue*blueFactor;
  int r = rv;
  int g = gv;
  int b = bv;
  analogWrite(redPin,r);
  analogWrite(bluePin,g);
  analogWrite(greenPin, b);
In the end it gives quite usefull results.
I tinkered around with it a bit further and got a complete working program: ADJD-S371. It contains a lot of rubbish and the organization of the code is less than perfect. But at least it gives a good overview of how it can be done.

Comments

Popular posts from this blog

The Difference Between LEGO MINDSTORMS EV3 Home Edition (#31313) and LEGO MINDSTORMS Education EV3 (#45544)

http://robotsquare.com/2013/11/25/difference-between-ev3-home-edition-and-education-ev3/ This article covers the difference between the LEGO MINDSTORMS EV3 Home Edition and LEGO MINDSTORMS Education EV3 products. Other articles in the ‘difference between’ series: * The difference and compatibility between EV3 and NXT ( link ) * The difference between NXT Home Edition and NXT Education products ( link ) One robotics platform, two targets The LEGO MINDSTORMS EV3 robotics platform has been developed for two different target audiences. We have home users (children and hobbyists) and educational users (students and teachers). LEGO has designed a base set for each group, as well as several add on sets. There isn’t a clear line between home users and educational users, though. It’s fine to use the Education set at home, and it’s fine to use the Home Edition set at school. This article aims to clarify the differences between the two product lines so you can decide which...

Let’s ban PowerPoint in lectures – it makes students more stupid and professors more boring

https://theconversation.com/lets-ban-powerpoint-in-lectures-it-makes-students-more-stupid-and-professors-more-boring-36183 Reading bullet points off a screen doesn't teach anyone anything. Author Bent Meier Sørensen Professor in Philosophy and Business at Copenhagen Business School Disclosure Statement Bent Meier Sørensen does not work for, consult to, own shares in or receive funding from any company or organisation that would benefit from this article, and has no relevant affiliations. The Conversation is funded by CSIRO, Melbourne, Monash, RMIT, UTS, UWA, ACU, ANU, ASB, Baker IDI, Canberra, CDU, Curtin, Deakin, ECU, Flinders, Griffith, the Harry Perkins Institute, JCU, La Trobe, Massey, Murdoch, Newcastle, UQ, QUT, SAHMRI, Swinburne, Sydney, UNDA, UNE, UniSA, UNSW, USC, USQ, UTAS, UWS, VU and Wollongong. ...

Logic Analyzer with STM32 Boards

https://sysprogs.com/w/how-we-turned-8-popular-stm32-boards-into-powerful-logic-analyzers/ How We Turned 8 Popular STM32 Boards into Powerful Logic Analyzers March 23, 2017 Ivan Shcherbakov The idea of making a “soft logic analyzer” that will run on top of popular prototyping boards has been crossing my mind since we first got acquainted with the STM32 Discovery and Nucleo boards. The STM32 GPIO is blazingly fast and the built-in DMA controller looks powerful enough to handle high bandwidths. So having that in mind, we spent several months perfecting both software and firmware side and here is what we got in the end. Capturing the signals The main challenge when using a microcontroller like STM32 as a core of a logic analyzer is dealing with sampling irregularities. Unlike FPGA-based analyzers, the microcontroller has to share the same resources to load instructions from memory, read/write th...