Trying out the Avago ADJD-S371-Q999 Color Sensor

This is another sub-experiment leading up to a project that I’ve only tangentially mentioned on this blog so far: a robot that automatically sorts and places Perler Beads. The robot starts with a pile of unsorted beads, and after it separates them into single beads, it needs a way to accurately sense the color of each individual bead.

I thought I’d try out the Avago ADJD-S371-Q999 Color Sensor, (Data sheet) stocked by Spark Fun. They also have a handy evaluation board:

The sensor has a built-in A/D converter with a simple I2C interface. I chose to use a Propeller prototyping board for the test, and display the resulting colors on my PC. For my test, I wanted to continuously compensate for ambient light, and just measure the light reflected off of the bead. So I integrate the sensed light levels twice, once with the LED off and once with it on, then I subtract them. A little scaling and white balance calibration, and I have an RGB color suitable for display on a monitor. The calibration is certainly nothing scientific, but it’s more than enough to get a visibly recognizable color, and I’m hoping it should be plenty accurate enough to differentiate the various bead colors.

(Video on YouTube)


CON
  _clkmode = xtal1 + pll16x
  _xinfreq = 5_000_000

  TV  = 12
  SCL = 4
  LED = 6

  ' Registers
  CTRL          = $00
  CONFIG        = $01
  CAP_RED       = $06
  CAP_GREEN     = $07
  CAP_BLUE      = $08
  CAP_CLEAR     = $09
  INT_RED_LO    = $0A
  INT_RED_HI    = $0B
  INT_GREEN_LO  = $0C
  INT_GREEN_HI  = $0D
  INT_BLUE_LO   = $0E
  INT_BLUE_HI   = $0F
  INT_CLEAR_LO  = $10
  INT_CLEAR_HI  = $11
  DATA_RED_LO   = $40
  DATA_RED_HI   = $41
  DATA_GREEN_LO = $42
  DATA_GREEN_HI = $43
  DATA_BLUE_LO  = $44
  DATA_BLUE_HI  = $45
  DATA_CLEAR_LO = $46
  DATA_CLEAR_HI = $47
  OFFSET_RED    = $48
  OFFSET_GREEN  = $49
  OFFSET_BLUE   = $4A
  OFFSET_CLEAR  = $4B

  ' Register bits
  CTRL_GSSR     = 1 << 0
  CTRL_GOFS     = 1 << 1
  CONFIG_TOFS   = 1 << 0
  CONFIG_SLEEP  = 1 << 1
  CONFIG_EXTCLK = 1 << 2  
  
OBJ
  text : "tv_text"
  i2c : "Basic_I2C_Driver"
  term : "Parallax Serial Terminal"

PUB start | i, color[4]
  text.start(TV)
  term.start(115200)
  i2c.initialize(SCL)
  dira[LED]~~
  outa[LED]~~

  repeat
    differential(4, $1FF, @color)
    
    text.str(string(1, "Color:"))
    repeat i from 0 to 3
      text.out(13)
      text.hex(color[i], 8)
      term.char(" ")
      term.hex(color[i], 8)
    term.char(10)

      
PRI differential(reps, time, buffer) | fg[4], bg[4], i, settle_delay
  ' Take a differential reading, once with the light off and once with it on,
  ' in order to measure the reflected color of an object in front of the sensor.

  longfill(buffer, 0, 4)

  repeat reps
    outa[LED]~~
    integrate(time, @fg)

    outa[LED]~
    integrate(time, @bg)

    repeat i from 0 to 3
      LONG[buffer][i] += fg[i] - bg[i]

  repeat i from 0 to 3      
    LONG[buffer][i] #>= 0

PRI integrate(time, buffer) | t
  ' Integrate the incident light for a given number of timeslots

  longfill(buffer, 0, 4) 
  repeat while time > 0
  
    t := time <# $FFF
    time -= t
    
    regWrite16(INT_RED_LO, t)
    regWrite16(INT_GREEN_LO, t)
    regWrite16(INT_BLUE_LO, t)
    regWrite16(INT_CLEAR_LO, t)

    regWrite(CTRL, CTRL_GSSR)
    repeat while regRead(CTRL)  
    
    LONG[buffer] += regRead16(DATA_RED_LO)
    LONG[buffer][1] += regRead16(DATA_GREEN_LO)
    LONG[buffer][2] += regRead16(DATA_BLUE_LO)
    LONG[buffer][3] += regRead16(DATA_CLEAR_LO)
  
PRI regWrite(addr, data)
  i2c.start(SCL)
  i2c.write(SCL, $E8)
  i2c.write(SCL, addr)
  i2c.write(SCL, data)
  i2c.stop(SCL)

PRI regRead(addr)
  i2c.start(SCL)
  i2c.write(SCL, $E8)      ' Write address
  i2c.write(SCL, addr)
  i2c.start(SCL)           ' Repeated start condition
  i2c.write(SCL, $E9)
  result := i2c.read(SCL, 0)
  i2c.stop(SCL)

PRI regWrite16(addr, data)
  regWrite(addr, data & $FF)
  regWrite(addr + 1, data >> 8)

PRI regRead16(addr)
  result := regRead(addr) | (regRead(addr + 1) << 8)

import serial
import pygame
import sys

port = serial.Serial(port="COM23", baudrate="115200")

pygame.init()
surface = pygame.display.set_mode((512, 512))

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()

    color = [int(s, 16) for s in port.readline().split()]

    s = 0.5 
    whitePoint = [3910.0/3704, 1, 3910.0/2541]

    rgb = [color[i] * whitePoint[i] * s for i in range(3)]
    m = max(*rgb)
    if m > 255:
        rgb = [rgb[i] * 255.0 / m for i in range(3)]

    print color, rgb

    surface.fill(rgb)
    pygame.display.flip()