Thursday, September 4, 2014

Capacitive touch Mood/Ambilight [Full Tutorial]


This instructable is a quick write-up of my experience creating a multifunctional moodlight. Some basic knowledge of electronic circuits is expected. The project has not yet finished, some adding functionality  and tweaking has to be done but it is already functional. If you guys are enthousiast about this instructable I will update it.



At the heart of the system is an Arduino. It will process the input from USB or each of the Capacitive touch inputs and control the RGB light.
This instructable is split into three sections:
- The capacitive touch section covers the invisible input buttons
- The moodlight section covers the controlling of the moodlight
- The ambilight section covers input by serial port, processing RGB values generated by a computer program to control the lights.

Disclaimer: Electronics can be dangerous, you yourself are responsible for any damage done. Some code is collected from forums and might not contain the name of its owner. Please let me know and I'll add your name.

Step 1: Item list

The following components are needed for this instructable:

- Arduino+USB cable
- Breadboard
- Computer power supply
- 3x RGB strips, check out dealextreme.com.
- 3x TIP120 FETs, like http://uk.farnell.com/stmicroelectronics/tip120/darlington-transistor-to-220/dp/9804005
- A bunch of resistors (6* 10 kiloOhm, 3 * 2 megaOhm)
- A lot of wire.
- Tools

Capacitive touch
- Metal rings for groundplates
- Copper wire or plate
- Something to build it into (like a bookshelf:)


Step 2: Capacitive Touch - Basics & Circuit

Since I was painting my bookshelfs, I had the oppurtunity to 'upgrade' them as well. I wanted to control the moodlight by means of invisible touch. At first, my plan was to use a dedicated IC for this (like the Atmel QT240). But then I stumbled upon a page explaining that the Arduino can emulate a capacitive sensor by software.

The electronic circuit can be found in the picture, the sensor is a spiralled copper wire (only one is shown for simplicity). Sensitivity is controlled by the resistors found before every pin. They can range from 1 MegaOhm (absolute touch) to 40 MegaOhm (12-24 inch away) depending on if absolute or near touch is needed (I ended up using 2M Ohm resistors). Experiment with the values until the sensor behaves like desired. It is a good idea to install some conducting surface (seperated by a thin non-conducting piece) connected to the circuits' ground at the back of each spiral. This way the sensors will be more stable and less influenced by noise.

Some more pictures about installing the sensors in a bookshelf. A plug is installed as well for easy connection with the circuit later on. Filler is used to conceal everything, and after that they are ready to be painted.

Step 3: Capacitive Touch - Code & Testing

The following source code can be used on the Arduino for debugging, check the values with the arduino serial monitor. Six values are generated. The first is a measure of the performance of the system. The second to sixth are the sensed values on every pin. The values should rise when nearing your finger. If not, check for bad connections and interference. The resistor values can be changed to determine sensitivity. By implementing an if-then structure which is activated at a certain logical treshold, a switch can be made. This will be used in the final arduino code.

More information, suggested to read: http://www.arduino.cc/playground/Main/CapSense

--- Arduino CapTouch Debugging Code ---
#include

void setup()  {
CapSense   cs_2_3 = CapSense(2,4);        // 10M resistor between pins 2 & 4, pin 4 is sensor pin, add wire, foil
CapSense   cs_2_4 = CapSense(2,7);        // 10M resistor between pins 2 & 7, pin 7 is sensor pin, add wire, foil
CapSense   cs_2_5 = CapSense(2,8);        // 10M resistor between pins 2 & 8, pin 8 is sensor pin, add wire, foil
CapSense   cs_2_6 = CapSense(2,12);        // 10M resistor between pins 2 & 12, pin 12 is sensor pin, add wire, foil
CapSense   cs_2_7 = CapSense(2,13);        // 10M resistor between pins 2 & 13, pin 13 is sensor pin, add wire, foil

void setup()                   

{
   Serial.begin(9600);
}

void loop()                   
{
    long start = millis();
    long total1 =  cs_2_3.capSense(30);
    long total2 =  cs_2_4.capSense(30);
    long total3 =  cs_2_5.capSense(30);
    long total4 =  cs_2_6.capSense(30);
    long total5 =  cs_2_7.capSense(30);   
  
    Serial.print(millis() - start);        // check on performance in milliseconds
    Serial.print("\t");                    // tab character for debug windown spacing

    Serial.print(total1);                  // print sensor output 1
    Serial.print("\t");
    Serial.print(total2);                  // print sensor output 2
    Serial.print("\t");
    Serial.print(total3);                // print sensor output 3
    Serial.print("\t");
    Serial.print(total4);                // print sensor output 4
    Serial.print("\t");
    Serial.println(total5);                // print sensor output 5

    delay(10);                             // arbitrary delay to limit data to serial port
}
--- END ---

Step 4: Mood light - Basics & Circuit

Now it is time to build the output part of the system. The arduino's PWM pins will be used to control each colour. PWM means Pulse Width Modulation, by switching a pin on and off very quickly the leds will be dimmed from 0 to 255. Every pin will be amplified by a FET. For now, the system has only one channel per color, meaning that all RGB strips will  be controlled at once and 3 PWM pins are needed (one for every colour). In the future I want to be able to control each of my four RGB strips. That means 4*3=12 PWM pins (and probably an Arduino Mega).

Ok, time for some schematics! This (see picture) is a basic representation of the circuit (will make a nicer one soon). The capacitive sensors are included as well (green part). Basically there are three components that have to be explained:

- FET
This is the amplifier I was talking about. It has a Gate, a Source and a Drain. It amplifies the senses a small current on the gate (connected to the Arduino) and opens the way for the RGB strip that is driven on 12 volts. Source should be on +12V, drain on GND (Ground). Check the specifcations sheet of your FET for exact pinout. Every RGB channel should be positioned before its own FET. In this sense, it is acting like an Arduino controlled switch.

- RGB strip
This 12 volts RGB strip is of the common anode (+) type. Meaning, that the common wire should be connected to +12V and the current is sinked through each of the seperate colour channels. The strip has incorporated resistors, so no worries about that!

- Resistors
Three 10k resistors will make sure that the FET won't turn on when they are not supposed to turn on. Three others will limit the maximum current the FET will drain. The top three resistors are already in the RGB strip.

I soldered USB cables to the RGB strips so I can modularly connect them easily. Plugs from an old hub are placed on my breadboard. Use an old computer power supply for juice, 12V for powering the RGB strip and eventually 5V for the circuit if you want it to run without the USB cable.

Step 5: Mood light - Code & Control

The mood light is controlled by the capacitive sensors. For now, I only programmed sensors 2 & 3 for colour changing. The other sensors have no function as of yet. Here is the code:


--- Arduino Mood Control Code ---
#include

const boolean invert = true;
const long timeout = 10000;

// Capacitive sensor declaration
CapSense   In1 = CapSense(2,4);        // 2M resistor between pins 4 & 2, pin 2 is sensor pin, add wire, foil
CapSense   In2 = CapSense(2,7);        // 2M resistor between pins 4 & 6, pin 6 is sensor pin, add wire, foil
CapSense   In3 = CapSense(2,8);        // 2M resistor between pins 4 & 8, pin 8 is sensor pin, add wire, foil
CapSense   In4 = CapSense(2,12);        // 2M resistor between pins 4 & 8, pin 8 is sensor pin, add wire, foil
CapSense   In5 = CapSense(2,13);        // 2M resistor between pins 4 & 8, pin 8 is sensor pin, add wire, foil

// PWM Pin declarations
int PinR1 = 3;
int PinG1 = 5;
int PinB1 = 6;

// Other variables
int Color1 = 128;        // start at a red like colour
int Brightness1 = 255;    // start at full brightness
int RedValue1, GreenValue1, BlueValue1;    // The RGB components

void setup()                   
{    // set sensor timeout values
    In1.set_CS_AutocaL_Millis(timeout);
    In2.set_CS_AutocaL_Millis(timeout);
    In3.set_CS_AutocaL_Millis(timeout);
    In4.set_CS_AutocaL_Millis(timeout);
    In5.set_CS_AutocaL_Millis(timeout);
}

void loop()                   
{
    long start = millis();
    long total1 =  In1.capSense(30);
    long total2 =  In2.capSense(30);
    long total3 =  In3.capSense(30);
    long total4 =  In4.capSense(30);
    long total5 =  In5.capSense(30);

    if (total2 > 150) {
          Color1++;         // increment the color
          if(Color1 > 255) {   //
             Color1 = 0;
          }
    } else if (total3 > 200) {
          Color1--;         // decrement the color
          if(Color1 < 0) {   //
             Color1 = 255;
          }
    // convert hue to rgb
    hueToRGB(Color1, Brightness1);
       
    // write colors to PWM pins
    analogWrite(PinR1, RedValue1);
    analogWrite(PinG1, GreenValue1);
    analogWrite(PinB1, BlueValue1);
}

// function to convert a color to its Red, Green, and Blue components.

void hueToRGB(int hue, int brightness)

    unsigned int scaledHue = (hue * 6);
    unsigned int segment = scaledHue / 256; // segment 0 to 5 around the color wheel
    unsigned int segmentOffset = scaledHue - (segment * 256);    // position within the segment
   
    unsigned int compliment = 0;
    unsigned int prev = (brightness * ( 255 -  segmentOffset)) / 256;
    unsigned int next = (brightness *  segmentOffset) / 256;

  if(invert)
    {
    brightness = 255-brightness;
    compliment = 255;
    prev = 255-prev;
    next = 255-next;
    }
   
    switch(segment ) {
    case 0:    // red
    RedValue1 = brightness;
    GreenValue1 = next;
    BlueValue1 = compliment;
    break;
    case 1:     // yellow
    RedValue1 = prev;
    GreenValue1 = brightness;
    BlueValue1 = compliment;
    break;
    case 2:     // green
    RedValue1 = compliment;
    GreenValue1 = brightness;
    BlueValue1 = next;
    break;
    case 3:    // cyan
    RedValue1 = compliment;
    GreenValue1 = prev;
    BlueValue1 = brightness;
    break;
    case 4:    // blue
    RedValue1 = next;
    GreenValue1 = compliment;
    BlueValue1 = brightness; 
    break;
   case 5:    // magenta
    default:
    RedValue1 = brightness;
    GreenValue1 = compliment;
    BlueValue1 = prev;
    break;
    }
}
--- END ---

Step 6: Ambi light - Arduino side

Of course, it would be totally cool to be able to control the mood light from your computer. For example to create an ambilight or a sound controlled disco. This section focusses on the ambilight part, in the future I will add more functionality.

Well, there's no additional circuitry because it is all available in the Arduino. What we are going to use is the serial communiction capabilities and some 'Processing 1.0' software. Hook-up your arduino to your computer by an USB cable (if you were uploading sketches to it, it already is). 

For the arduino, well have to add some extra code for serial communication. The code will switch to listen mode, turning of the capacitive sensors as long as it receives RGB values from the computer. It then sets the RGB values to the PWM pins. This is my final code for now, check for the changes yourself:

--- Arduino Ambilight Code ---
#include

const boolean invert = true;
const long timeout = 10000;
long commStart = 0;
char val;

// Capacitive sensor declaration
CapSense   In1 = CapSense(2,4);        // 2M resistor between pins 4 & 2, pin 2 is sensor pin, add wire, foil
CapSense   In2 = CapSense(2,7);        // 2M resistor between pins 4 & 6, pin 6 is sensor pin, add wire, foil
CapSense   In3 = CapSense(2,8);        // 2M resistor between pins 4 & 8, pin 8 is sensor pin, add wire, foil
CapSense   In4 = CapSense(2,12);        // 2M resistor between pins 4 & 8, pin 8 is sensor pin, add wire, foil
CapSense   In5 = CapSense(2,13);        // 2M resistor between pins 4 & 8, pin 8 is sensor pin, add wire, foil

// PWM Pin declarations
int PinR1 = 3;
int PinG1 = 5;
int PinB1 = 6;

// Other variables
int Color1 = 128;        // start at a red like colour
int Brightness1 = 255;    // start at full brightness
int RedValue1, GreenValue1, BlueValue1;    // The RGB components

void setup()                   
{
    Serial.begin(9600);    // start serial communiction
// set sensor timeout values
    In1.set_CS_AutocaL_Millis(timeout);
    In2.set_CS_AutocaL_Millis(timeout);
    In3.set_CS_AutocaL_Millis(timeout);
    In4.set_CS_AutocaL_Millis(timeout);
    In5.set_CS_AutocaL_Millis(timeout);
}

void loop()                   
{
    long start = millis();
    long total1 =  In1.capSense(30);
    long total2 =  In2.capSense(30);
    long total3 =  In3.capSense(30);
    long total4 =  In4.capSense(30);
    long total5 =  In5.capSense(30);
   
    if (Serial.available()) {             // If data is available to read,
     val = Serial.read();                // read it and store it in val
     commStart = millis();
 
   if (val == 'S') {                   //If start char is recieved,
     while (!Serial.available()) {}    //Wait until next value.
     RedValue1 = Serial.read();              //Once available, assign.

     while (!Serial.available()) {}    //Same as above.
     GreenValue1 = Serial.read();

     while (!Serial.available()) {}
     BlueValue1 = Serial.read();
     }
     Serial.print(RedValue1);
     Serial.print(GreenValue1);
     Serial.println(BlueValue1);
  } else if ((millis() - commStart) > 1000) {
   
 if (total2 > 150) {
          Color1++;         // increment the color
          if(Color1 > 255) {   //
             Color1 = 0;
          }
    } else if (total3 > 200) {
          Color1--;         // decrement the color
          if(Color1 < 0) {   //
             Color1 = 255;
          }
    }
    hueToRGB(Color1, Brightness1);
    }
   
    analogWrite(PinR1, RedValue1);
    analogWrite(PinG1, GreenValue1);
    analogWrite(PinB1, BlueValue1);
}

// function to convert a color to its Red, Green, and Blue components.

void hueToRGB(int hue, int brightness)

    unsigned int scaledHue = (hue * 6);
    unsigned int segment = scaledHue / 256; // segment 0 to 5 around the color wheel
    unsigned int segmentOffset = scaledHue - (segment * 256);    // position within the segment
   
    unsigned int compliment = 0;
    unsigned int prev = (brightness * ( 255 -  segmentOffset)) / 256;
    unsigned int next = (brightness *  segmentOffset) / 256;

  if(invert)
    {
    brightness = 255-brightness;
    compliment = 255;
    prev = 255-prev;
    next = 255-next;
    }
   
    switch(segment ) {
    case 0:    // red
    RedValue1 = brightness;
    GreenValue1 = next;
    BlueValue1 = compliment;
    break;
    case 1:     // yellow
    RedValue1 = prev;
    GreenValue1 = brightness;
    BlueValue1 = compliment;
    break;
    case 2:     // green
    RedValue1 = compliment;
    GreenValue1 = brightness;
    BlueValue1 = next;
    break;
    case 3:    // cyan
    RedValue1 = compliment;
    GreenValue1 = prev;
    BlueValue1 = brightness;
    break;
    case 4:    // blue
    RedValue1 = next;
    GreenValue1 = compliment;
    BlueValue1 = brightness; 
    break;
   case 5:    // magenta
    default:
    RedValue1 = brightness;
    GreenValue1 = compliment;
    BlueValue1 = prev;
    break;
    }
}
--- END ---

Step 7: Ambi light - Computer side

On the side of the computer a Processing 1.0 sketch is run, see processing.org . This little (somewhat messy) program calculates the average screencolor at every instant and sends this to the serial port. It is very basic as of yet and it could use some tweaking, but it works very well! I will update it in the future for multiple separate RGB strips and screen sections. You could also do that yourself, the language is quite straightforward.

Here is the code:

--- Processing 1.0 Code ---
import processing.serial.*;
import java.awt.AWTException;
import java.awt.Robot;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;

PImage screenShot;
Serial myPort;

static public void main(String args[]) {
  PApplet.main(new String[] {
    "--present", "shooter"  }
  );
}

void setup() {
  size(100,100); //size(screen.width, screen.height);
  // Print a list of the serial ports, for debugging purposes:
  println(Serial.list());

  // I know that the first port in the serial list on my mac
  // is always my  FTDI adaptor, so I open Serial.list()[0].
  // On Windows machines, this generally opens COM1.
  // Open whatever port is the one you're using.
  String portName = Serial.list()[0];
  myPort = new Serial(this, portName, 9600);
}

void draw () {
  //image(screenShot,0,0, width, height);
  screenShot = getScreen();
  color kleur = color(0,0,0);
  kleur = colour(screenShot);
  //myPort.write(int(red(kleur))+','+int(green(kleur))+','+int(blue(kleur))+13);
  //myPort.write(int(red(kleur)));
  //myPort.write(',');
  //myPort.write(int(green(kleur)));
  //myPort.write(',');
  //myPort.write(int(blue(kleur)));
  //myPort.write(13);
  fill(kleur);
  rect(30, 20, 55, 55);
}

color colour(PImage img) {
  int cols = (img.width);
  int rows = (img.height);
  int dimension = (img.width*img.height);
  int r = 0;
  int g = 0;
  int b = 0;
  img.loadPixels();

// Ga elke pixel langs (dimension)
for (int i = 0; i < (dimension/2); i++) {
  r = r + ((img.pixels[i] >> 16) & 0xFF);
  g = g + ((img.pixels[i] >> 8) & 0xFF);
  b = b + (img.pixels[i] & 0xFF);
}

int mean_r = r/(dimension/2);
int mean_g = g/(dimension/2);
int mean_b = b/(dimension/2);

color mean_clr = color(mean_r, mean_g, mean_b);
  myPort.write('S');
  myPort.write(mean_r);
  myPort.write(mean_g);
  myPort.write(mean_b);
return (mean_clr);
}

PImage getScreen() {
  GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
  GraphicsDevice[] gs = ge.getScreenDevices();
  DisplayMode mode = gs[0].getDisplayMode();
  Rectangle bounds = new Rectangle(0, 0, mode.getWidth(), mode.getHeight());
  BufferedImage desktop = new BufferedImage(mode.getWidth(), mode.getHeight(), BufferedImage.TYPE_INT_RGB);

  try {
    desktop = new Robot(gs[0]).createScreenCapture(bounds);
  }
  catch(AWTException e) {
    System.err.println("Screen capture failed.");
  }

  return (new PImage(desktop));
}

--- END ---

Step 8: Result

And this is the result, it is actually at the bottom side of my bed. I still need to replace the cloth, it will diffuse the light more. More pictures on that soon.



I hope you like this instructable and I also hope it is a base for your own creativity. Because of time-constraints I wrote it very quickly. You might need to have some basic arduino/electronics knowledge to understand it but I plan to update it in the future if it is well received.

0 comments:

Post a Comment