Sunday, April 15, 2012

Mood Lamp Touch Version 1.0



Horse of a different colour

The finishing touches on this touchy project ended up taking longer than I expected, leaving me with much less time (and patience) to blog about it.



Alterations

Since the last post on it, I modified the touch lamp code to change colours on tap-and-hold. It was a little less error-prone and far more intuitive than detecting fingers at a distance from the touch area.


[ I hate vero board... yes, I still hate it... ]

Frustrations

Moving it to the vero board was relatively painless. What really caused me headaches was the reassembly of the lamp itself. When using capacitive detection, all wires must be very well insulated and accounted for.

Version Cutoff

After having to assemble, test, disassemble, solder, un-solder, assemble, ad-nauseum for a few hours I gave up on trying to fix all of the bugs or not-so-great features of it and called it “Version 1.0”. Eventually the state of it will bug me enough to make version 2. Until then, at least I have my fancy mood lamp touch with colour therapy to relax with.

Alpha Bugs/Shortcomings

  1. The emitter is far too high in the lamp diffuser, creating a bright band of light in the middle of the lamp instead of at the base.
  2. A single RGB module is far too weak to produce enough light to fill a room. More will need to be added.
  3. Occasionally, due to either the vero board wiring or the internal wire arrangement or some other variable in the sensitivity of the code, the lamp will turn itself off instead of change colours when touched and held. It’s not too frequent so it should be ok for now.
  4. The project severely underuses the Atmega328P. Don’t know what I can do about this one, really. Smaller chips could be bought and used but it’s more effort and time (hence more cost). I optimised a bunch of the HSV to RGB code to produce a hex file that was about half the size that it started at… If you’re interested in recreating this, it’s worth mentioning that the hex is under 8K and would easily fit on an ATtiny85 (slightly less wasteful).

Code

Should you want to make this project yourself, here’s the sketch for it. It was thrown together pretty quickly so I apologise for the many ragged edges. You’ll first need to install the CapSense library by Paul Badger.

Note: Because it was easier on the vero, I changed D4 and D5 to D6 and D7. Be sure to change it back if you follow the schematic to the letter.

#include <CapSense.h>

/* colours */

const long cRed     = 0xFF0000;
const long cGreen   = 0x00FF00;
const long cBlue    = 0x0000FF;

const long cYellow  = cRed | cGreen;
const long cCyan    = cGreen | cBlue;
const long cMagenta = cRed | cBlue;

/* shifts */

const short cRedShift   = 16;
const short cGreenShift = 8;
const short cBlueShift  = 0;

int CKI = 2;
int SDI = 3;

boolean touchPresent = false;

boolean isOn = true;

long toggledFromTouchAtTime = 1L;

long touchesBeganAtTime = 1L;
long currentTouchHoldTime = 1L;
long lastTouchHoldTime = 1L;

CapSense   cs_4_5 = CapSense(6,7); 

#define STRIP_LENGTH 1 // Number of RGBLED modules connected
long currentColor = 0L;
long lastColor = 0L;

void setup() {
  pinMode(SDI, OUTPUT);
  pinMode(CKI, OUTPUT);
  cs_4_5.set_CS_AutocaL_Millis(0xFFFFFFFF);
}

void touchesDidBegin()
{
  touchPresent = true;
  touchesBeganAtTime = millis();
  currentTouchHoldTime = 0;
}

void touchesDidContinue()
{
  currentTouchHoldTime = millis() - touchesBeganAtTime;
}

void touchesDidEnd()
{
  touchPresent = false;
  lastTouchHoldTime = millis() - touchesBeganAtTime;
  currentTouchHoldTime = 0;
}

long highestVal = 0;
long red = 0x00;
long green = 0xFF;
long blue = 0x00;
long *modColor = NULL;
boolean addColor;

void updateCurrentColor()
{
  currentColor = (red << cRedShift) + (green << cGreenShift) + (blue << cBlueShift);
}

void changeColor()
{
  switch(currentColor){

      case cRed:
        // add green
        modColor = &green;
        addColor = true;
      break;

      case cYellow:
        // subtract red
        modColor = &red;
        addColor = false;
      break;

      case cGreen:
        // add blue
        modColor = &blue;
        addColor = true;
      break;

      case cCyan:
        // subtract green
        modColor = &green;
        addColor = false;
      break;

      case cBlue:
        // add red
        modColor = &red;
        addColor = true;
      break;

      case cMagenta:
        // subtract blue
        modColor = &blue;
        addColor = false;
      break;

      default:
      break;
    }

    // modify the colour
    if (addColor){
      *modColor = *modColor + 1;
    } else {
      *modColor = *modColor - 1;
    }

    updateCurrentColor();
}

void loop() {

  long total1 =  cs_4_5.capSense(30);

  // calibrate

  if (highestVal == 0){
    for (int i = 0; i < 100; i++){
      if (highestVal < total1){
        highestVal = total1;
      }
      total1 = cs_4_5.capSense(30);
      delay(10);
    }
  }

  // touch recognition

  if (total1 >= highestVal + 30){
    if (!touchPresent){
      touchesDidBegin();
    } else {
      touchesDidContinue();
    }
  } else if (touchPresent){
    touchesDidEnd();
  }

  // and the colour bit

  if (isOn && currentTouchHoldTime > 500){
    changeColor();
  } else if ((!isOn || (!touchPresent && lastTouchHoldTime < 500)) && (touchesBeganAtTime > toggledFromTouchAtTime + 200)) {
    toggledFromTouchAtTime = touchesBeganAtTime;
    isOn = !isOn;
  }

  if (isOn) {
    updateCurrentColor();
  } else {
    currentColor = 0;
  }

  if (currentColor != lastColor) {
    post_frame(currentColor);
    lastColor = currentColor;
  }

  delay(50);
}

void post_frame (long led_color) {
  for(int LED_number = 0; LED_number < STRIP_LENGTH; LED_number++)
  {
    long this_led_color = led_color; //24 bits of color data

    for(byte color_bit = 23 ; color_bit != 255 ; color_bit--) {
      //Feed color bit 23 first (red data MSB)

      digitalWrite(CKI, LOW); //Only change data when clock is low

      long mask = 1L << color_bit;
      //The 1'L' forces the 1 to start as a 32 bit number, otherwise it defaults to 16-bit.

      if(this_led_color & mask) 
        digitalWrite(SDI, HIGH);
      else
        digitalWrite(SDI, LOW);

      digitalWrite(CKI, HIGH); //Data is latched when clock goes high
    }
  }

  //Pull clock low to put strip into reset/post mode
  digitalWrite(CKI, LOW);
  delayMicroseconds(500); //Wait for 500us to go into reset
}

2 comments:

  1. for problem 1: try to add a half or 2/3 flat cut tabletennisball over the leds to act as a defusor level 1, then somewhere else u can have another difusor as well. of course brightness drops even more, but light difuses very well then.

    ReplyDelete
  2. Thanks for that, Axel. Next time I'm at the $2 shop I might pick some up for a test run. Good thinking!

    ReplyDelete