Color Sensing using Arduino and the Wolfram Language

Suba Thomas
Wolfram Research, Inc.

ABSTRACT

This talk will demonstrate various ways in which the Wolfram Language Microcontroller Kit can be used to acquire color using a TCS34725 sensor. The sensor data is sent over I2C by integrating the currents from an array consisting  of red-filtered, green-filtered, blue-filtered, and unfiltered photodiodes. The microcontroller obtains the I2C data and then sends it over serial to Mathematica. The I2C and serial communication techniques are general, and can be adapted for other I2C and serial devices. The various approaches are compared and the talk also includes a potential application example.

TCS34725 light-to-digital converter

Package & functional block diagram

I2C

Physical setup

Prologue

Three approaches

Sensor library [github]

Wire library

Bottom up

Preparatory code

Needs["MicrocontrollerKit`"]

port:=First[FileNames["/dev/cu.usb*"],"p"]

createFile[]:=$file=CreateFile[]

SetAttributes[appendInputs, HoldAll];
appendInputs[CompoundExpression[inp_]|inp_:Except[_CompoundExpression]]:=(PutAppend[Unevaluated[inp],$file];inp)
appendInputs[CompoundExpression[args__]]:=ReleaseHold[Map[appendInputs, Hold[{args}],{2}]]

{sb,db,eb}={19,44,17};

parseData[{sb,rest__}]:=With[{c=SequenceSplit[{rest},{db}]},Switch[Length[c],3,ToExpression[FromCharacterCode[#]]&/@c,_,{}]]
parseData[___]:={}

RandomReal[{0,1},3]
Flatten[{{sb},Riffle[ToCharacterCode[ToString[#]]&/@%,{db}]}]

parseData[%]

rgbPieChart[rgb:{Repeated[_?NumericQ,{3}]}]:= Show[
Graphics[{EdgeForm[Thick],RGBColor@@rgb,Annulus[{0,0},{1,3}]},ImageSize->Medium],

PieChart[rgb,ChartStyle->{Red,Green,Blue},ChartLabels->(Framed[#,Background->White,FrameStyle->None,RoundingRadius->10]&/@rgb)]]

rgbPieChart[___]:= {}

displayRGBData[]:= Grid[{{ListLinePlot[rgbList\[Transpose],PlotStyle->Table[RGBColor[UnitVector[3,i]],{i,3}], PlotTheme->"Detailed",ImageSize->Large],rgbPieChart[rgbList[[-1]]]}}]

rgbList=RandomReal[{0.25,1},{10,3}];
displayRGBData[]

openAndRead[]:= (
dev=DeviceOpen["Serial",port];

rgbList={};
task=SessionSubmit@ScheduledTask[
If[DeviceExecute[dev,"SerialReadyQ"],
rgb=parseData[DeviceReadBuffer[dev,"ReadTerminator"->eb]];
If[rgb≠{},AppendTo[rgbList, rgb]];
DeviceExecute[dev,"ReadFlush"];
],
sp];
)

close[]:= (TaskRemove[task]; DeviceClose[dev])

TCS34725 sensor library

ℱSensor=createFile[];

$Pre=appendInputs;

sp=0.05;

sys=NonlinearStateSpaceModel[{{}, Which[cl<3500, 0, True,#/cl]&/@{rr,rg,rb}},{},{{cl,1},rr,rg,rb},SamplingPeriod->sp]

lib=FileNameJoin[{$HomeDirectory,"Documents","Arduino","libraries","Adafruit_TCS34725","Adafruit_TCS34725.cpp"}];

i={"<Wire.h>","Adafruit_TCS34725.h"};

u="Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_50MS, TCS34725_GAIN_4X)";

inits="tcs.begin()";

l={
"uint16_t rr, rg, rb, cl", 
"tcs.getRawData(&rr, &rg, &rb, &cl)",
"cl"
};

ics=Join[
{<|"Type"->"ExternalLibrary","Loop"->l|>}
,
<|"Type"->"ExternalLibrary","Loop"->#|>&/@{"rr","rg","rb"}
];

ocs=Table[ "Serial",{3}];

μ𝒸=<|"Target"->"ArduinoUno","IncludeFiles"->i,"Utilities"->u,"Initializations"->inits,"Inputs"->ics,"Outputs"->ocs|>;

ℳSensor=MicrocontrollerEmbedCode[sys,μ𝒸,port,<|"Libraries"->lib,"Language"->"Wiring"|>]

$Pre=.

openAndRead[];

Dynamic[displayRGBData[]]

close[]

Wire library

Sensor operation

Code

ℱWire=createFile[];

$Pre=appendInputs;

sp=0.05;

sys=NonlinearStateSpaceModel[{{}, Which[cl<3500, 0, True,#/cl]&/@{rr,rg,rb}},{},{{cl,1},rr,rg,rb},SamplingPeriod->sp]

The value of the ATIME register for 50 ms integration time:

With[{intTime=50},Round[256−((Quantity[intTime, "ms", milliseconds, "Milliseconds"])/(Quantity[2.4 , "ms", milliseconds, "Milliseconds"]))]];
ATIME50MS=ToUpperCase[IntegerString[%,16]]

The various addresses and bit values:

I2CADDRESS=Interpreter["HexInteger"]["29"];

COMMAND=Interpreter["HexInteger"]["80"];

ENABLEREG=Interpreter["HexInteger"]["00"];
ENABLEPON=Interpreter["HexInteger"]["01"];
ENABLEAEN=Interpreter["HexInteger"]["02"];

ATIMEREG=Interpreter["HexInteger"]["01"];
ATIME50MS=Interpreter["HexInteger"][ATIME50MS];

CONTROLREG=Interpreter["HexInteger"]["0F"];
CONTROLGAIN4X=Interpreter["HexInteger"]["01"];

CDATALREG=Interpreter["HexInteger"]["14"];

writeData[reg_, dbyte_]:=Sequence[
"START",
BitOr[COMMAND,reg],
dbyte
]

i2cinit="Initialization"->{
writeData[ATIMEREG,ATIME50MS],
writeData[CONTROLREG,CONTROLGAIN4X],
writeData[ENABLEREG, BitOr[ENABLEPON,ENABLEAEN]],
"STOP"};

i2cread=Flatten[{"START", BitOr[COMMAND,CDATALREG],"START",Table[{"READ","ACK"},{7}],"READ","STOP"}];

i2cspecs=I2CADDRESS->{i2cread,i2cinit};

MapIndexed[(#1[Repeated[_,2 #2-2],lbyte_,hbyte_, ___]:=COperator[BitOr,{COperator[BitShiftLeft,{hbyte, 8}],lbyte}])&,{cfun,rfun,gfun,bfun}];

Grid[Table[{fun,ToCCodeString[fun@@Table["b"<>ToString@i,{i,0,7}]]},{fun,{cfun,rfun,gfun,bfun}}],Background->{None,{{LightBlue,White}}},Frame->All]

ics=Table["A4"-><|"Type"-> "I2C","SlaveAddress"->I2CADDRESS, "Conversion"->fun|>,{fun,{cfun,rfun,gfun,bfun}}]

ocs=Table[ "Serial",{3}];

ℳWire=MicrocontrollerEmbedCode[sys, <|"Target"->"ArduinoUno", "Inputs"-> ics, "Outputs"-> ocs|>,port,<|"I2C"->i2cspecs,"Language"->"Wiring"|>]

$Pre=.

openAndRead[];

Dynamic[displayRGBData[]]

close[]

Sans library

ℱBottomUp=createFile[];

$Pre=appendInputs;

ℳBottomUp=MicrocontrollerEmbedCode[sys, <|"Target"->"ArduinoUno", "Inputs"-> ics, "Outputs"-> ocs|>,port,<|"I2C"->i2cspecs|>]

$Pre=.

openAndRead[];

Dynamic[displayRGBData[]]

close[]

Code size comparison

With[{sizes=MapThread[Join[{#1},##2]&,{FileByteCount/@{ℱSensor,ℱWire,ℱBottomUp},#[{"SourceCodeByteCount","MachineCodeByteCount"}]&/@{ℳSensor, ℳWire, ℳBottomUp}}]},
BarChart[sizes,ChartLabels->{{"sensor library","wire library","bottom up"}, None},ChartLegends->{"WL code","source code", "machine code"},PlotTheme->"Detailed",ChartStyle->106]]

DeleteFile[{ℱSensor,ℱWire, ℱBottomUp}]

An application

dev=DeviceOpen["Serial",port]

colors={};
task=SessionSubmit@ScheduledTask[
If[DeviceExecute[dev,"SerialReadyQ"],
With[{c=parseData[DeviceReadBuffer[dev,"ReadTerminator"->eb]]},
If[MatchQ[c,{_,_,_}], AppendTo[colors, RGBColor[c]]]]
],
sp];

Dynamic[ChromaticityPlot[Flatten@{"sRGB",colors},PlotStyle->PointSize[0.02]]]

colors={};

close[]

Summary

TCS34725 sensor & Arduino Uno

Adafruit product page [link]

Adafruit library [github]

Datasheet [link]

Microcontroller Kit

MicrocontrollerEmbedCode [documentation] [online]

I2C [documentation] [online]

Product page [link]

Wolfram Language

Device Framework

Visualization, dynamic interactivity, etc