Netduino Début with Color Sensor

Netduino
Netduino
Netduino

Over the last few weeks Harford Hackerspace has had the pleasure of beta testing the Netduino. The Netduino is a development board with a form factor similar to the Arduino. Care was taken when designing the Netduino to ensure compatibility with most of the existing Arduino shields. That’s about where the similarities of the two devices ends.

The Netduino uses Microsoft’s Open Source .net Micro Framework SDK along with Visual Studio C# (or VS C# Express) as the primary development environment. C# application developers will be able to quickly adapt to the .net Micro Framework and start programming microcontrollers in a matter of minutes. However, this does not let them off the hook for learning the basics of electronics.

After downloading and installing Visual Studio C# Express 2010, the .net Micro Framework 4.1 SDK , and the Secret Labs Netduino SDK we started with the “Hello World” of microcontrollers and blinked the on-board LED. That was easy…! You’ll be able to get details to do this yourself from the Netduino website.

SparkFun Color Sensor

Sparkfun Color Sensor
ADJD-S371-Q999

I purchased the ADJD-S371 color sensor(right) from SparkFun several months ago with the intention of connecting it to a PIC microcontroller and creating a device that will print the HTML color name (i.e. DarkRed) of an object that it sees. I’m particularly interested in this project because I happen to be Color Blind. There are several off the shelf products available but where is the fun in that?

The ADJD-S371 is a 4 channel RGB+Clear sensor and uses the 2-wire I2C protocol to communicate with the microcontroller.  It’s a complex little device that will test your puzzle solving skills. What I mean is that the device does not simply give you the correct color when you ask for it. You’ll have to play around with the Gain calibration algorithm and come up with something that works best for your application. However, for simple testing purposes you can use the code described in this tutorial. Here’s a quick video to demonstrate the project:

The code was originally created by Nathan Seidle for the PIC microcontroller and was ported and modified for the Arduino by Marcus from Interactive Matter. Paul King and I ported it once again to the C# Language for the .net Micro Framework.

Colorimeter Schematic
Click to Enlarge
The schematic to the right shows how we hooked up the Netduino. There are a few things to note. First, we are using an external LED for our light source so there is no need to connect the LED pin on the ADJD-S371-Q999. Also, note that we skip a pin when we hooked up the RGB LED. This is because our protoshield was broke and that pin was not working. You can use any of the Digital IO Pins. You just need to change a little code when setting up the Output Ports. Finally, the RGB LED could be hooked to 3.3v or 5v. You may need to adjust the resistor sizes for your RGB LED anyways.

C# Code

1
2
3
4
5
6
using System;
using System.IO.Ports;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;

Lines 1 through 6 define a namespace. This allows you to use classes or types without having to type out the full namespace where those types exist. For example, instead of typing:
SecretLabs.NETMF.Hardware.Netduino.Pins.ONBOARD_LED
We can simply type:
Pins.ONBOARD_LED

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
namespace Squintz.Colorimeter
{
    public class Program
    {
        public static byte I2C_ADDRESS = 0x74;       // 7bit
        public static byte REG_CAP_RED      =0x06;
        public static byte REG_CAP_GREEN    =0x07;
        public static byte REG_CAP_BLUE     =0x08;
        public static byte REG_CAP_CLEAR    =0x09;

        public static byte REG_INT_RED_LO   =0x0A;
        public static byte REG_INT_RED_HI   =0x0B;
        public static byte REG_INT_GREEN_LO =0x0C;
        public static byte REG_INT_GREEN_HI =0x0D;
        public static byte REG_INT_BLUE_LO  =0x0E;
        public static byte REG_INT_BLUE_HI  =0x0F;
        public static byte REG_INT_CLEAR_LO =0x10;
        public static byte REG_INT_CLEAR_HI =0x11;

        public static byte REG_DATA_RED_LO  =0x40;
        public static byte REG_DATA_RED_HI  =0x41;
        public static byte REG_DATA_GREEN_LO=0x42;
        public static byte REG_DATA_GREEN_HI=0x43;
        public static byte REG_DATA_BLUE_LO =0x44;
        public static byte REG_DATA_BLUE_HI =0x45;
        public static byte REG_DATA_CLEAR_LO=0x46;
        public static byte REG_DATA_CLEAR_HI=0x47;

        public static I2CDevice rtc = new I2CDevice(new I2CDevice.Configuration(I2C_ADDRESS, 100));

        public static byte[] readBuffer = new byte[1];

Lines 7 through 33 are constants defined in the Color Sensors documentation. Line 35 creates a new I2C device object named rtc. We use this object to actually send the data from the Netduino to the Color Sensor. I2C is a bus protocol which means that multiple devices can share the same wires. So the I2CDevice object requires us to give it a slave address. We defined this as the constant I2C_Address on line 11. The I2CDevice object also requires us to specify a rate in Kilohertz(Khz). 100Khz seemed to do that trick. We tried other speeds without much luck.

Line 37 creates a variable of type byte array. Note that we are only initializing the size of the array to 1. The color sensor does not actually need an array of bytes but the I2CDevice.CreateReadTransaction requires a byte array as one of its parameters and does not have an overloaded method for using just a byte.

38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
        public static void Main()
        {
            OutputPort redpin = new OutputPort(Pins.GPIO_PIN_D10, true);
            OutputPort greenpin = new OutputPort(Pins.GPIO_PIN_D9, true);
            OutputPort bluepin = new OutputPort(Pins.GPIO_PIN_D7, true);

            int cc = 0;
            int red = 0;
            int green = 0;
            int blue = 0;

            int clearGain;
            int colorGain;

            int num_samples;

            int i;
           
            while (true)
            {
                clearGain = getClearGain();
                set_gain(REG_INT_CLEAR_LO, clearGain);
                colorGain = getColorGain();
                set_gain(REG_INT_RED_LO, colorGain);
                set_gain(REG_INT_GREEN_LO, colorGain);
                set_gain(REG_INT_BLUE_LO, colorGain);

                num_samples = 10;

                for (i=0; i<num_samples ;i ++)
                {
                  Program.performMeasurement();
                  cc +=get_readout(REG_DATA_CLEAR_LO);
                  red +=get_readout(REG_DATA_RED_LO);
                  green +=get_readout(REG_DATA_GREEN_LO);
                  blue +=get_readout(REG_DATA_BLUE_LO);
                }
                cc/=num_samples;
                red /=num_samples;
                green /=num_samples;
                blue /=num_samples;

                redpin.Write(false);
                greenpin.Write(false);
                bluepin.Write(false);

               
                if (red > green && red > blue)
                    redpin.Write(true);
                else
                    if (green > red && green > blue)
                        greenpin.Write(true);
                    else
                        if (blue > red && blue > green)
                            bluepin.Write(true);


                Debug.Print("C: " + (byte)(cc >> 2) + " " + "R:" + (byte)(red >> 2) + " " + "G:" + (byte)(green >> 2) + " " + "B:" + (byte)(blue >> 2));
                Thread.Sleep(500);
            }
        }

Lines 38 is the main entry point for our program. On lines 40 through 42 we setup 3 pins which we are using to control an RGB LED. By adding the SecretLabs.NETMF.Hardware.Netduino.dll to our list of resources and declaring it with the using statement on line 6 we don’t have to remember the actual microcontrollers pin numbers. SecretLabs has already looked all this up for us and they allow us to use Pins.xxxxx. You notice that the intellisense aka code complete feature of Visual Studio automatically pops up a context menu allowing you to select from a list of available pins.

[*UPDATE: If you use the SecretLabs SDK Installer you can start a new "Netduino Application" which removes the need for manually adding the dll's to your resource list and also removes the need to manually add the SecretLabs using statements.]

On line 56 we create an infinite loop so that our program never ends. Without this our program would only run once and then terminate.

Lines 58 through 64 is where you could get creative. The color sensors datasheet and application note are a little fuzzy on explaining the proper way to calibrate and set the gain of your sensor. However, you MUST set the gain for each color before you request the color. If you fail to set the gain then you will get low results. We learned this the hard way. We just could not figure out why we only got numbers from 0 to 3!

Lines 65 through 78 is where we sample the colors through the use of a few helper methods which you’ll read about below. The key thing to note here is that the more samples you take the more stable your numbers will appear. We are taking 10 samples and adding the results together and dividing by 10 to get the average value.

Lines 80 through 93 is where we set the color of our RGB LED. We are using a very simple algorithm to determine the most dominate color and turning on just that leg of the RGB LED. We experimented with mixing colors but our LED was not diffused enough to mix the colors together. We were able to see the individual colors which did not have a good effect.

On Line 95 we call Debug.Print and output the values to Visual Studios output window. The clear value indicates an overall light intensity which the Red, Green, and Blue values are the intensity as seen through a light filter.

99
100
101
102
        public static void performMeasurement()
        {
            set_register(0x00,0x01); // start sensing
        }

Now we start getting deep into the color sensors protocol. To tell the color sensor to take a sample we set the register 0×00 to the value of 0×01. The color sensor then takes a sample and stores it.

103
104
105
106
        public static int get_readout(byte readRegister)
        {
            return read_register(readRegister) + ( read_register((byte)(readRegister + 0x01))<<8);
        }

The color sensor stores its color values in two 8 bit registers. So. on lines 103 through 106, get_readout() takes the first register as a parameter and assumes the next register is just 1 more address above that register. So we read the value of the first register and then read the value of the second register and then shift the value of the second register 8 bits to the left leaving us with the full 16 bit integer. However, only 10 bits are actually put into the integer because the High registers only return the two least significant bits and the Low register returns 7 Bits.

107
108
109
110
111
112
113
114
115
116
117
        public static void set_gain(byte gainRegister, int gain)
        {
              if (gain <4096)
              {
                byte hi = (byte)(gain >> 8);
                byte lo = (byte)(gain);

                set_register(gainRegister, lo);
                set_register((byte)(gainRegister+1), hi);
              }
        }

Each color has two of its own gain registers. We defined these in our constants in the first few lines. Here we again assume that since we know the register address of the lower register, we can just add 1 to that address to get the higher register address. Then we assign the gain values through the help of set_register().

118
119
120
121
122
123
124
125
126
        public static void set_register(byte r, byte v)
        {

            rtc.Execute(new I2CDevice.I2CTransaction[]
            {
                    I2CDevice.CreateWriteTransaction(new byte[] {r, v})
               
             }, 5000);
        }

set_register is where the magic happens. Here we create a new byte array which contains two values. The first is r for register and the second is v for value. We pass this byte array to a new I2CDevice.CreateWriteTransaction object and then pass that object to the rtc.Execute method. You should recall that rtc is our I2CDevice object which knows about the slave address of our color sensor. So, in a nutshell our set_register function is where we package everything up and send it on its way.

127
128
129
130
131
132
133
134
135
136
        public static byte read_register(byte r)
        {
            rtc.Execute(new I2CDevice.I2CTransaction[] {
                    I2CDevice.CreateWriteTransaction(new byte[] {r})
                    ,
                I2CDevice.CreateReadTransaction(readBuffer)
                }, 5000);

          return readBuffer[0];
        }

On Lines 127 through 136 we are sending a request for our color values. We pass it ‘r’ which is the color we want to read. Remember that this happens twice for each color in order to get the full value. We wait 5000 milliseconds or 5 seconds for the return before we timeout.

137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
        public static int getClearGain()
        {
            int gainFound = 0;
            int upperBox = 4096;
            int lowerBox = 0;
            int half = 0;
            int halfValue;

            while (gainFound == 0)
            {
                half = ((upperBox - lowerBox) / 2) + lowerBox;
                //no further halfing possbile
                if (half == lowerBox)
                {
                    gainFound = 1;
                }
                else
                {
                    set_gain(REG_INT_CLEAR_LO, half);
                    performMeasurement();
                    halfValue = get_readout(REG_DATA_CLEAR_LO);

                    if (halfValue > 1000)
                    {
                        upperBox = half;
                    }
                    else if (halfValue < 1000)
                    {
                        lowerBox = half;
                    }
                    else
                    {
                        gainFound = 1;
                    }
                }
            }
            return half;
        }

Lines 137 to 174 is where you can again play around to perfect your results. You’ll need to derive a way to obtain a gain value which compensates for the ambient light and all the other environmental factors which may change the sensitivity. I’ll be honest. I’m not too sure what is going on here. You should check out Interactive Matter for more information about this method.

175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
        public static int getColorGain()
        {
            int gainFound = 0;
            int upperBox = 4096;
            int lowerBox = 0;
            int half = 0;
            int halfValue;

            while (gainFound == 0)
            {
                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();
                    halfValue = 0;

                    halfValue = System.Math.Max(halfValue, get_readout(REG_DATA_RED_LO));
                    halfValue = System.Math.Max(halfValue, get_readout(REG_DATA_GREEN_LO));
                    halfValue = System.Math.Max(halfValue, get_readout(REG_DATA_BLUE_LO));

                    if (halfValue > 1000)
                    {
                        upperBox = half;
                    }
                    else if (halfValue < 1000)
                    {
                        lowerBox = half;
                    }
                    else
                    {
                        gainFound = 1;
                    }
                }
            }
            return half;
        }
    }
}

Finally, lines 175 to 220 tries to establish a suitable gain for each color. This will be called 3 times because each color has a different sensitivity. You can get creative here and come up with your own algorithm which suits your specific application. If you had a controlled environment then in theory this would be very simple. Make sure you post back here and let us know if you stumble across a better way to determine the gain values.

Comments

Leave a Reply

%d bloggers like this: