Colour sensor on .NET Gadgeteer (TCS3414)

Last post I was looking at the individually addressable LEDs and used the ‘ColorSense’ module to detect colours. (But did not get very good results)

This post addresses the issues with the colour sensor.

I am using the ColorSense REV 1.2 which has a TCS3414 digital colour sensor and 4 white LEDs to illuminate any sources. Each sensor has 16 photodiodes, 4 per channel (R,G,B,Clear)  each channel has a 16 bit ADC and we are communicating to the module using I2C. The original driver form GHI can be found in Codeplex: http://gadgeteer.codeplex.com/SourceControl/changeset/view/24955#333941

There are a few settings you may wish to change in order to calibrate she sensor for the task you have in mind. (you may even want to change these values during operation)

1) Integration time.

You can change the length of time that the sensor takes per integration cycle. There are 3 settings and 12ms is the default. (10ms / 400ms being the other options.) The code to enable changes to this is shown below. (If you are getting saturated, lower the integration time)

  1. public struct TimingRegister
  2. {
  3.     //http://www.ghielectronics.com/downloads/man/ColorSense_TCS3404_TCS3414-A.pdf
  4.     public static byte RegisterAddress = 0×81;
  5.     public static byte FallingEdge_FreeRunning_12ms = 0×00;
  6.     public static byte FallingEdge_FreeRunning_100ms = 0×01;
  7.     public static byte FallingEdge_FreeRunning_400ms = 0×02;
  8. }

  1. public void SetIntegrationTime(byte value)
  2. {
  3.     softwareI2C.Write(colorAddress, new byte[] { TimingRegister.RegisterAddress, value});
  4. }

2) Setting the Gain and the prescalar.

The gain and prescalar are set in a single operation. The gain provides “gain control for all four parallel ADC channels” and can be used to ensure the ADC range is modified to avoid saturation. I.e. if the output is always low, then increasing the gain will improve the sensitivity and the channel outputs will increase. The gain can be changed to 4 discrete multipliers, 1x, 4x, 16x and 64x – 64x would make the sensor very sensitive. (Saturation can be avoided by lowering the integration time)

The prescalar divides the ADC output, thus reducing the sensitivity of the ADC. There are 7 different prescalars -  divide by 1,2,4,8,16,32,64.

I have put the gain and prescalar options into a struct with a supporting method to change them:

  1. public struct GainRegister
  2. {
  3.     public static byte RegisterAddress = 0×87;
  4.     //Reserved bit (7 = high)
  5.     static byte Reserved = 0×80;
  6.     //Gain – This field switches the common analog gain of the four ADC channels. Four gain modes are provide
  7.     //bit 5:4
  8.     static byte Gain1x = 0×00;
  9.     static byte Gain4x = 0×10;
  10.     static byte Gain16x = 0×20;
  11.     static byte Gain64x = 0×30;
  12.     //Prescaler. This field controls a 6-bit digital prescaler and divider. The prescaler reduces the
  13.     //sensitivity of each ADC integrator as shown below:
  14.     //Bit 2:0
  15.     static byte PreScalerDivide1 = 0×00;
  16.     static byte PreScalerDivide2 = 0×01;
  17.     static byte PreScalerDivide4 = 0×02;
  18.     static byte PreScalerDivide8 = 0×03;
  19.     static byte PreScalerDivide16 = 0×04;
  20.     static byte PreScalerDivide32 = 0×05;
  21.     static byte PreScalerDivide64 = 0×06;
  22.     public static byte Gain1x_PreScalerDivide1 = (byte)(Reserved | Gain1x | PreScalerDivide1);
  23.     public static byte Gain4x_PreScalerDivide1 = (byte)(Reserved | Gain4x | PreScalerDivide1);
  24.     public static byte Gain16x_PreScalerDivide1 = (byte)(Reserved | Gain16x | PreScalerDivide1);
  25.     public static byte Gain64x_PreScalerDivide1 = (byte)(Reserved | Gain64x | PreScalerDivide1);
  26.     public static byte Gain1x_PreScalerDivide2 = (byte)(Reserved | Gain1x | PreScalerDivide2);
  27.     public static byte Gain1x_PreScalerDivide4 = (byte)(Reserved | Gain1x | PreScalerDivide4);
  28.     public static byte Gain1x_PreScalerDivide8 = (byte)(Reserved | Gain1x | PreScalerDivide8);
  29.     public static byte Gain1x_PreScalerDivide16 = (byte)(Reserved | Gain1x | PreScalerDivide16);
  30.     public static byte Gain1x_PreScalerDivide32 = (byte)(Reserved | Gain1x | PreScalerDivide32);
  31.     public static byte Gain1x_PreScalerDivide64 = (byte)(Reserved | Gain1x | PreScalerDivide64);
  32.     public static byte Gain4x_PreScalerDivide2 = (byte)(Reserved | Gain4x | PreScalerDivide2);
  33.     public static byte Gain4x_PreScalerDivide4 = (byte)(Reserved | Gain4x | PreScalerDivide4);
  34.     public static byte Gain4x_PreScalerDivide8 = (byte)(Reserved | Gain4x | PreScalerDivide8);
  35.     public static byte Gain4x_PreScalerDivide16 = (byte)(Reserved | Gain4x | PreScalerDivide16);
  36.     public static byte Gain4x_PreScalerDivide32 = (byte)(Reserved | Gain4x | PreScalerDivide32);
  37.     public static byte Gain4x_PreScalerDivide64 = (byte)(Reserved | Gain4x | PreScalerDivide64);
  38.     public static byte Gain16x_PreScalerDivide2 = (byte)(Reserved | Gain16x | PreScalerDivide2);
  39.     public static byte Gain16x_PreScalerDivide4 = (byte)(Reserved | Gain16x | PreScalerDivide4);
  40.     public static byte Gain16x_PreScalerDivide8 = (byte)(Reserved | Gain16x | PreScalerDivide8);
  41.     public static byte Gain16x_PreScalerDivide16 = (byte)(Reserved | Gain16x | PreScalerDivide16);
  42.     public static byte Gain16x_PreScalerDivide32 = (byte)(Reserved | Gain16x | PreScalerDivide32);
  43.     public static byte Gain16x_PreScalerDivide64 = (byte)(Reserved | Gain16x | PreScalerDivide64);
  44.     public static byte Gain64x_PreScalerDivide2 = (byte)(Reserved | Gain64x | PreScalerDivide2);
  45.     public static byte Gain64x_PreScalerDivide4 = (byte)(Reserved | Gain64x | PreScalerDivide4);
  46.     public static byte Gain64x_PreScalerDivide8 = (byte)(Reserved | Gain64x | PreScalerDivide8);
  47.     public static byte Gain64x_PreScalerDivide16 = (byte)(Reserved | Gain64x | PreScalerDivide16);
  48.     public static byte Gain64x_PreScalerDivide32 = (byte)(Reserved | Gain64x | PreScalerDivide64);
  49.     public static byte Gain64x_PreScalerDivide64 = (byte)(Reserved | Gain64x | PreScalerDivide64);
  50.  
  51. }

  1. public void SetGain(byte value)
  2. {
  3.     softwareI2C.Write(colorAddress, new byte[] { GainRegister.RegisterAddress,value });
  4. }

3) Enable disable

It may also be useful to enable/disable the module.

  1. public struct ControlRegister
  2. {
  3.     public static byte RegisterAddress = 0×80;
  4.  
  5.     public static byte EnableADC_PowerOn = 0×03;
  6.     public static byte DisableADC_PowerOff = 0×00;
  7. }

  1. public void EnableSensor(bool enable)
  2. {
  3.     if (enable)
  4.     {
  5.         softwareI2C.Write(colorAddress, new byte[] { ControlRegister.RegisterAddress, ControlRegister.EnableADC_PowerOn });
  6.     }
  7.     else
  8.     {
  9.         softwareI2C.Write(colorAddress, new byte[] { ControlRegister.RegisterAddress, ControlRegister.DisableADC_PowerOff });
  10.     }
  11. }

Example

For the task I have in mind setting the following provides me with RGB values between 0-65k and rarely gets saturated (About as sensitive as I dare push it) . Note there are public methods on this module to change these parameters.

  1. this.EnableSensor(true);//Power sensor on
  2. this.SetIntegrationTime(TimingRegister.FallingEdge_FreeRunning_100ms);//Set the time span for integration
  3. this.SetGain(GainRegister.Gain64x_PreScalerDivide2);//Set Gain and PreScalar.

Complete code below

  1. using System;
  2. using Microsoft.SPOT;
  3.  
  4. using GT = Gadgeteer;
  5. using GTM = Gadgeteer.Modules;
  6. using GTI = Gadgeteer.Interfaces;
  7.  
  8. namespace Gadgeteer.Modules.GHIElectronics
  9. {
  10.     //http://www.ghielectronics.com/downloads/man/ColorSense_TCS3404_TCS3414-A.pdf    
  11.     //ADDRESS REGISTER NAME REGISTER FUNCTION – Set MSB high ( OR register address with 0×80)
  12.     // COMMAND Specifies register address
  13.     //00h CONTROL Control of basic functions
  14.     //01h TIMING Integration time/gain control
  15.     //02h INTERRUPT Interrupt control
  16.     //03h INT SOURCE Interrupt source
  17.     //04h ID Part number/ Rev ID
  18.     //07h GAIN ADC gain control
  19.     //08h LOW_THRESH_LOW_BYTE Low byte of low interrupt threshold
  20.     //09h LOW_THRESH_HIGH_BYTE High byte of low interrupt threshold
  21.     //0Ah HIGH_THRESH_LOW_BYTE Low byte of high interrupt threshold
  22.     //0Bh HIGH_THRESH_HIGH_BYTE High byte of high interrupt threshold
  23.     //0Fh  SMBus block read (10h through 17h)
  24.     //10h DATA1LOW Low byte of ADC green channel
  25.     //11h DATA1HIGH High byte of ADC green channel
  26.     //12h DATA2LOW Low byte of ADC red channel
  27.     //13h DATA2HIGH High byte of ADC red channel
  28.     //14h DATA3LOW Low byte of ADC blue channel
  29.     //15h DATA3HIGH High byte of ADC blue channel
  30.     //16h DATA4LOW Low byte of ADC clear channel
  31.     //17h DATA4HIGH High byte of ADC clear channel
  32.  
  33.  
  34.     /// <summary>
  35.     /// A ColorSense module for Microsoft .NET Gadgeteer
  36.     /// </summary>
  37.     public class ColorSense : GTM.Module
  38.     {
  39.         private static GTI.DigitalOutput LEDControl;
  40.         private static GTI.SoftwareI2C softwareI2C;
  41.         private static byte[] readRegisterData = new byte[2];
  42.         private const byte colorAddress = 0×39;//Address for the Colour sense module on I2C
  43.  
  44.         // Note: A constructor summary is auto-generated by the doc builder.
  45.         /// <summary></summary>
  46.         /// <param name="socketNumber">The socket that this module is plugged in to.</param>
  47.         public ColorSense(int socketNumber)
  48.         {
  49.             Socket socket = Socket.GetSocket(socketNumber, true, this, null);
  50.  
  51.             socket.EnsureTypeIsSupported(new char[] { 'X', 'Y' }, this);
  52.  
  53.             LEDControl = new GTI.DigitalOutput(socket, Socket.Pin.Three, false, this);
  54.  
  55.             softwareI2C = new GTI.SoftwareI2C(socket, Socket.Pin.Five, Socket.Pin.Four, this);
  56.  
  57.             // Send COMMAND to access control register for chip power-up
  58.             // Send to power-up chip
  59.             this.EnableSensor(true);//Power sensor on
  60.             this.SetIntegrationTime(TimingRegister.FallingEdge_FreeRunning_100ms);//Set the time span for integration
  61.             this.SetGain(GainRegister.Gain64x_PreScalerDivide2);//Set Gain and PreScalar.
  62.         }
  63.  
  64.         /// <summary>
  65.         /// Turn the sensor on/off
  66.         /// </summary>
  67.         /// <param name="enable"></param>
  68.         public void EnableSensor(bool enable)
  69.         {
  70.             if (enable)
  71.             {
  72.                 softwareI2C.Write(colorAddress, new byte[] { ControlRegister.RegisterAddress, ControlRegister.EnableADC_PowerOn });
  73.             }
  74.             else
  75.             {
  76.                 softwareI2C.Write(colorAddress, new byte[] { ControlRegister.RegisterAddress, ControlRegister.DisableADC_PowerOff });
  77.             }
  78.         }
  79.  
  80.         /// <summary>
  81.         /// The TIMING register controls the synchronization and integration time of the ADC channels. The Timing
  82.         /// Register settings apply to all four ADC channels.
  83.         /// </summary>
  84.         /// <param name="value"></param>
  85.         public void SetIntegrationTime(byte value)
  86.         {
  87.             softwareI2C.Write(colorAddress, new byte[] { TimingRegister.RegisterAddress, value});
  88.         }
  89.  
  90.         /// <summary>
  91.         /// http://www.ghielectronics.com/downloads/man/ColorSense_TCS3404_TCS3414-A.pdf
  92.         /// The Gain register provides a common gain control adjustment for all four parallel ADC output channels. Two
  93.         //gain bits [5:4] in the Gain Register allow the relative gain to be adjusted from 1 to 64 in 4 increments. The
  94.         //advantage of the gain adjust is to extend the dynamic range of the light input up to a factor of 64 before analog
  95.         //or digital saturation occurs. If analog saturation has occurred, lowering the gain sensitivity will likely prevent
  96.         //analog saturation especially when the integration time is relatively short. For longer integration times, the 16-bit
  97.         //output could be in digital saturation (64K). If lowering the gain to 1 does not prevent digital saturation from
  98.         //occurring, the use of PRESCALER can be useful.
  99.         //The PRESCALER is 3 bits [2:0] in the gain register that divides down the output count (i.e. shifts the LSB of the
  100.         //count value to the right). The PRESCALER adjustment range is divide by 1 to 64 in multiples of 2.
  101.         //The most sensitive gain setting of the device would be when GAIN is set to 11b (64), and PRESCALER is set
  102.         //to 000b (divide by 1). The least sensitive part setting would be GAIN 00 (1) and PRESCALER 110 (divide by
  103.         //64). If the part continues to be in digital saturation at the least sensitive setting, the integration time can be
  104.         //lowered (see Timing Register section).
  105.         /// </summary>
  106.         /// <param name="value">GainRegister value </param>
  107.         public void SetGain(byte value)
  108.         {
  109.             softwareI2C.Write(colorAddress, new byte[] { GainRegister.RegisterAddress,value });
  110.         }
  111.  
  112.         /// <summary>
  113.         /// Toggles the on-board LEDs to the passed in state.
  114.         /// </summary>
  115.         /// <param name="LEDState">State to set the LEDs to.</param>
  116.         public void ToggleOnboardLED(bool LEDState)
  117.         {
  118.             LEDControl.Write(LEDState);
  119.         }
  120.  
  121.         /// <summary>
  122.         /// Reads the current color from the sensor and returns the results
  123.         /// </summary>
  124.         /// <returns>Returns an instance of the ColorChannels structure, holding the current measurement of color values.</returns>
  125.         public ColorChannels ReadColorChannels()
  126.         {
  127.             ColorChannels returnData;
  128.  
  129.             byte[] TransmitBuffer = new byte[1];
  130.  
  131.             TransmitBuffer[0] = 0×90; // Send COMMAND to access Green Color Channel register for chip
  132.             returnData.Green = readWord(0×90, TransmitBuffer)[0];
  133.             TransmitBuffer[0] = 0×91;
  134.             returnData.Green |= (uint)readWord(0×91, TransmitBuffer)[0] << 8;
  135.             //returnData.Green = 256 * (uint)readWord(TransmitBuffer)[0] + returnData.Green;
  136.  
  137.             TransmitBuffer[0] = 0×92; // Send COMMAND to access Red Color Channel register for chip
  138.             returnData.Red = readWord(0×92, TransmitBuffer)[0];
  139.             TransmitBuffer[0] = 0×93;
  140.             returnData.Red |= (uint)readWord(0×93, TransmitBuffer)[0] << 8;
  141.  
  142.             TransmitBuffer[0] = 0×94; // Send COMMAND to access Blue Color Channel register for chip
  143.             returnData.Blue = readWord(0×94, TransmitBuffer)[0];
  144.             TransmitBuffer[0] = 0×95;
  145.             returnData.Blue |= (uint)readWord(0×95, TransmitBuffer)[0] << 8;
  146.  
  147.             TransmitBuffer[0] = 0×96; // Send COMMAND to access Clear Channel register for chip
  148.             returnData.Clear = readWord(0×96, TransmitBuffer)[0];
  149.             TransmitBuffer[0] = 0×97;
  150.             returnData.Clear |= (uint)readWord(0×97, TransmitBuffer)[0] << 8;
  151.  
  152.             return returnData;
  153.         }
  154.  
  155.         private byte[] readWord(byte address, byte[] CommandBytes)
  156.         {
  157.             softwareI2C.WriteRead(colorAddress, CommandBytes, readRegisterData);
  158.             return readRegisterData;
  159.         }
  160.  
  161.         /// <summary>
  162.         /// Structure to hold color data
  163.         /// </summary>
  164.         public struct ColorChannels
  165.         {
  166.             /// <summary>
  167.             /// Intensity of green-filtered channel
  168.             /// </summary>
  169.             public uint Green;
  170.             /// <summary>
  171.             /// Intensity of red-filtered channel
  172.             /// </summary>
  173.             public uint Red;
  174.             /// <summary>
  175.             /// Intensity of blue-filtered channel
  176.             /// </summary>
  177.             public uint Blue;
  178.             /// <summary>
  179.             /// Intensity of non-filtered channel
  180.             /// </summary>
  181.             public uint Clear;
  182.         }
  183.  
  184.         /// <summary>
  185.         /// Enable / disable
  186.         /// </summary>
  187.         public struct ControlRegister
  188.         {
  189.             public static byte RegisterAddress = 0×80;
  190.             public static byte EnableADC_PowerOn = 0×03;
  191.             public static byte DisableADC_PowerOff = 0×00;
  192.         }
  193.  
  194.         /// <summary>
  195.         /// Integration time
  196.         /// </summary>
  197.         public struct TimingRegister
  198.         {
  199.             public static byte RegisterAddress = 0×81;
  200.             public static byte FallingEdge_FreeRunning_12ms = 0×00;
  201.             public static byte FallingEdge_FreeRunning_100ms = 0×01;
  202.             public static byte FallingEdge_FreeRunning_400ms = 0×02;
  203.         }
  204.  
  205.         /// <summary>
  206.         /// ADC Gain control- Gain = ADC adjustment, PreScaler = divides down the digital output
  207.         /// </summary>
  208.         public struct GainRegister
  209.         {
  210.             public static byte RegisterAddress = 0×87;
  211.             //Reserved bit (7 = high)
  212.             static byte Reserved = 0×80;
  213.             //Gain – This field switches the common analog gain of the four ADC channels. Four gain modes are provide
  214.             //bit 5:4
  215.             static byte Gain1x = 0×00;
  216.             static byte Gain4x = 0×10;
  217.             static byte Gain16x = 0×20;
  218.             static byte Gain64x = 0×30;
  219.             //Prescaler. This field controls a 6-bit digital prescaler and divider. The prescaler reduces the
  220.             //sensitivity of each ADC integrator as shown below:
  221.             //Bit 2:0
  222.             static byte PreScalerDivide1 = 0×00;
  223.             static byte PreScalerDivide2 = 0×01;
  224.             static byte PreScalerDivide4 = 0×02;
  225.             static byte PreScalerDivide8 = 0×03;
  226.             static byte PreScalerDivide16 = 0×04;
  227.             static byte PreScalerDivide32 = 0×05;
  228.             static byte PreScalerDivide64 = 0×06;
  229.             public static byte Gain1x_PreScalerDivide1 = (byte)(Reserved | Gain1x | PreScalerDivide1);
  230.             public static byte Gain4x_PreScalerDivide1 = (byte)(Reserved | Gain4x | PreScalerDivide1);
  231.             public static byte Gain16x_PreScalerDivide1 = (byte)(Reserved | Gain16x | PreScalerDivide1);
  232.             public static byte Gain64x_PreScalerDivide1 = (byte)(Reserved | Gain64x | PreScalerDivide1);
  233.             public static byte Gain1x_PreScalerDivide2 = (byte)(Reserved | Gain1x | PreScalerDivide2);
  234.             public static byte Gain1x_PreScalerDivide4 = (byte)(Reserved | Gain1x | PreScalerDivide4);
  235.             public static byte Gain1x_PreScalerDivide8 = (byte)(Reserved | Gain1x | PreScalerDivide8);
  236.             public static byte Gain1x_PreScalerDivide16 = (byte)(Reserved | Gain1x | PreScalerDivide16);
  237.             public static byte Gain1x_PreScalerDivide32 = (byte)(Reserved | Gain1x | PreScalerDivide32);
  238.             public static byte Gain1x_PreScalerDivide64 = (byte)(Reserved | Gain1x | PreScalerDivide64);
  239.             public static byte Gain4x_PreScalerDivide2 = (byte)(Reserved | Gain4x | PreScalerDivide2);
  240.             public static byte Gain4x_PreScalerDivide4 = (byte)(Reserved | Gain4x | PreScalerDivide4);
  241.             public static byte Gain4x_PreScalerDivide8 = (byte)(Reserved | Gain4x | PreScalerDivide8);
  242.             public static byte Gain4x_PreScalerDivide16 = (byte)(Reserved | Gain4x | PreScalerDivide16);
  243.             public static byte Gain4x_PreScalerDivide32 = (byte)(Reserved | Gain4x | PreScalerDivide32);
  244.             public static byte Gain4x_PreScalerDivide64 = (byte)(Reserved | Gain4x | PreScalerDivide64);
  245.             public static byte Gain16x_PreScalerDivide2 = (byte)(Reserved | Gain16x | PreScalerDivide2);
  246.             public static byte Gain16x_PreScalerDivide4 = (byte)(Reserved | Gain16x | PreScalerDivide4);
  247.             public static byte Gain16x_PreScalerDivide8 = (byte)(Reserved | Gain16x | PreScalerDivide8);
  248.             public static byte Gain16x_PreScalerDivide16 = (byte)(Reserved | Gain16x | PreScalerDivide16);
  249.             public static byte Gain16x_PreScalerDivide32 = (byte)(Reserved | Gain16x | PreScalerDivide32);
  250.             public static byte Gain16x_PreScalerDivide64 = (byte)(Reserved | Gain16x | PreScalerDivide64);
  251.             public static byte Gain64x_PreScalerDivide2 = (byte)(Reserved | Gain64x | PreScalerDivide2);
  252.             public static byte Gain64x_PreScalerDivide4 = (byte)(Reserved | Gain64x | PreScalerDivide4);
  253.             public static byte Gain64x_PreScalerDivide8 = (byte)(Reserved | Gain64x | PreScalerDivide8);
  254.             public static byte Gain64x_PreScalerDivide16 = (byte)(Reserved | Gain64x | PreScalerDivide16);
  255.             public static byte Gain64x_PreScalerDivide32 = (byte)(Reserved | Gain64x | PreScalerDivide64);
  256.             public static byte Gain64x_PreScalerDivide64 = (byte)(Reserved | Gain64x | PreScalerDivide64);
  257.  
  258.         }
  259.     }
  260. }

6 thoughts on “Colour sensor on .NET Gadgeteer (TCS3414)

  1. Pingback: Individually addressable multicolour LED’s on .NET Gadgeteer | Steven Johnston

  2. Pingback: Colour sensor + LED strip video | Steven Johnston

  3. I experienced much the same, holding up solid color’s to the sensor barely registers any difference on each channel. Complete beginner question for you if that’s okay, how would I go about taking your code and applying it? A quick copy paste into a c# class complains about 0×90; having an unexpected x. What silly thing am I not doing properly?

    • Hello,

      I am not too sure why you do not get the code to work, perhaps it is the way wordpress encodes it. The 0x (Zero,X) are used to notify the compiler that the value is a hex value. 1) Be sure you have set the variable to be a byte 2) type the 0x yourself, just to be sure that it is not a Unicode thing. Another way is to past into notepad , then copy into VS. – let me know how you get on
      Thanks Steven

      • You were correct, WordPress encoded the x. The code executes fine and makes the sensor more sensitive so thank you for sharing your code. It seems to pick up direct light sources (say, a back-lit color on my phone) but not indirect light reflected from a piece of cardboard.

  4. Hi,

    the code seems to execute fine but I am unable to print the colour values on the output.

    using colorSense.ReadColorChannels(); should be enough?

    Thank you very much. This post is great.

    Jaume Miro

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>