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.
- Spin source for the Propeller firmware (navi-misc/propeller/color-sensor-ADJDS371/avago-test.spin)
- Python source for the PC app (navi-misc/propeller/color-sensor-ADJDS371/avago-host.py)
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()

