Tuesday, May 19, 2009

Arduino Mood light controller

UPDATE: New source code and videos here.

Background
I love black and white, especially white. But I also love color, however I tend to change what type of color I like ALOT. So when saw the concept of coloring a surface with light I knew I had to try it at home.

The hardware I decided to use my new found love the Arduino micro controller to control some sort of light. At first I thought about using IKEAS DIODER but as MikMo pointed out the RGB LED strip from Deal Extreme is quite a much better deal. A little over $16 and free shipping from Hong Kong.


What I wanted to achive


  1. Select a specific color
  2. Pulsate the specified color
  3. Random color swash i.e. go smoothly from color to color.

Controlling the colors
I decided to use a ULN2003 with the Arduino to control the DX RGB LED strip. It can handle enough amps to drive the LED strip and it's also alot neater than multiple transistors.




Arduino control of LED strip via ULN2003
RGB LED strip 400 mA @ 12 V


R1 = 10K Potentiometer (Color selector) R2 = 100 Ohm (Pin protector)
R3 = 10K Ohm (Pulldown resistor) S1 = Switch (Mode selector)





One potentiometer to rule them all
When I first started out mixing RGB colors I used three potentiometers, one for each color. This ofcourse gives you a high resolution but also three different knobs. So inspired by the Philips LivingColors, I decided to streamline my color control using only one potentiometer connected to an analog in pin on the Arduino.



As you can see in the picture below the value 0 on the analog pin is full red and then changes through the color circle back to red as the value reaches 1023.



Note: I used Adobe Illustrator to create the image above, to help me think, but when using gradient fill in Illustrator some colors take up more space than others. At least it looks like that if you space them evenly. I don't know if this also is the case with my color mixer, but I haven't really bothered to figure that out.


At first I used alot of steps before I realized I could remove some of them. Orange for example is produced when you have more red than green on your way to yellow (full red and full green). But purple requires both full red and full blue. Hence some I could remove some steps and had to keep some of them.




Videos



Button presses in the video
Filmed in broad daylight, hence the colors don't really come out too well.
1. On - Select a color with the color selector.
2. Pulse - Pulsate the selected color.
3. Random/Cycle - Cycle randomly through the palette.
4. Off.



Pulse Mode
Filmed in a dark room with the RGB-strip hidden behind the laptop screen.





Random/Cycle Mode
Filmed in a dark room with the RGB-strip hidden behind the laptop screen.


Source code

UPDATE: New source code and videos here.


The finished code with four modes, also available as a download here.
Formatted for Blogger with: formatmysourcecode.blogspot.com
/*
RGB LED controller
4 modes: off, color select, color pulse and random cycle/pulse
By Markus Ulfberg 2009-05-19

Thanks to: Ladyada, Tom Igoe and
everyone at the Arduino forum for excellent
tutorials and everyday help.

*/

// set the ledPins
int ledRed = 10;
int ledGreen = 9;
int ledBlue = 11;

// color selector pin
int potPin = 1;

// lightMode selector
int switchPin = 2;

// light mode variable
// initial value 0 = off
int lightMode = 0;

// LED Power variables
byte redPwr = 0;
byte greenPwr = 0;
byte bluePwr = 0;

// Variables for lightMode 2
// variables for keeping pulse color
byte redPulse;
byte greenPulse;
byte bluePulse;

// Set pulse to down initially
byte pulse = 0;

// floating variables needed to be able to pulse a fixed color
float redFloat;
float greenFloat;
float blueFloat;

// the amount R,G & B should step up/down to display an fixed color
float redKoff;
float greenKoff;
float blueKoff;

// Variables for lightMode 3
// set the initial random colors
byte redNew = random(255);
byte greenNew = random(255);
byte blueNew = random(255);

// misc interface variables
// potVal keeps the value of the potentiometer
int potVal;
// value from the button (debounce)
int switchVal;
int switchVal2;
// buttonState registers if the button has changed
int buttonState;

void setup()
{
pinMode(ledRed, OUTPUT);
pinMode(ledGreen, OUTPUT);
pinMode(ledBlue, OUTPUT);

pinMode(potPin, INPUT);

pinMode(switchPin, INPUT);
buttonState = digitalRead(switchPin);

// serial for debugging purposes only
Serial.begin(9600);
}

void loop()
{
switchVal = digitalRead(switchPin);      // read input value and store it in val
delay(10);                         // 10 milliseconds is a good amount of time
switchVal2 = digitalRead(switchPin);     // read the input again to check for bounces
if (switchVal == switchVal2) {                 // make sure we got 2 consistant readings!
if (switchVal != buttonState) {          // the button state has changed!
if (switchVal == LOW) {                // check if the button is pressed
if (lightMode == 0) {          // light is off
lightMode = 1;             // light is on and responds to pot
} else {
if (lightMode == 1) {
lightMode = 2;           // light pulsates in the latest color from pot
} else {
if (lightMode == 2) {
lightMode = 3;           // light changes randomly
} else {
lightMode = 0;             // light is off 
}
}
}
}
}
buttonState = switchVal;                 // save the new state in our variable
}
if (lightMode == 0) {      // turn light off
analogWrite(ledRed, 0);
analogWrite(ledGreen, 0);
analogWrite(ledBlue, 0);
}
if (lightMode == 1) {        // set fixed color

// read the potentiometer position
potVal = analogRead(potPin);


// RED > ORANGE > YELLOW
if (potVal > 0 && potVal < 170) {
redPwr = 255;
bluePwr = 0;
analogWrite(ledRed, redPwr);
greenPwr = map(potVal, 0, 170, 0, 255);
analogWrite(ledGreen, greenPwr);
analogWrite(ledBlue, bluePwr);
}

// YELLOW > LIME?? > GREEN
if (potVal > 170 && potVal < 341) {
greenPwr = 255;
bluePwr = 0;
analogWrite(ledGreen, greenPwr);
redPwr = map(potVal, 341, 170, 0, 255);
analogWrite(ledRed, redPwr);
analogWrite(ledBlue, bluePwr);
}

// GREEN > TURQOUISE
if (potVal > 341 && potVal < 511) {
greenPwr = 255;
redPwr = 0;
analogWrite(ledGreen, greenPwr);
bluePwr = map(potVal, 341, 511, 0, 255);
analogWrite(ledBlue, bluePwr);
analogWrite(ledRed, redPwr);
}

// TURQOUISE > BLUE
if (potVal > 511 && potVal < 682) {
bluePwr = 255;
redPwr = 0;
analogWrite(ledBlue, bluePwr);
greenPwr = map(potVal, 682, 511, 0, 255);
analogWrite(ledGreen, greenPwr);
analogWrite(ledRed, redPwr);
}

// BLUE > PURPLE
if (potVal > 682 && potVal < 852) {
bluePwr = 255;
greenPwr = 0;
analogWrite(ledBlue, bluePwr);
redPwr = map(potVal, 682, 852, 0, 255);
analogWrite(ledRed, redPwr);
analogWrite(ledGreen, greenPwr);
}

// PURPLE > RED
if (potVal > 852 && potVal < 1023) {
redPwr = 255;
greenPwr = 0;
analogWrite(ledRed, redPwr);
bluePwr = map(potVal, 1023, 852, 0, 255);
analogWrite(ledBlue, bluePwr);
analogWrite(ledGreen, greenPwr);
}
redFloat = float(redPwr);
greenFloat = float(greenPwr);
blueFloat = float(bluePwr);

redKoff = redFloat / 255;
greenKoff = greenFloat / 255;
blueKoff = blueFloat / 255;

redPulse = redPwr;
greenPulse = greenPwr;
bluePulse = bluePwr;

/*
// Debug
Serial.print("redFLoat: ");
Serial.print(redFloat, DEC);
Serial.print(" redPwr: ");
Serial.print(redPwr, DEC);
Serial.print(" greenFloat: ");
Serial.print(greenFloat, DEC);
Serial.print(" greenPwr: ");
Serial.print(greenPwr, DEC);
Serial.print(" blueFloat: ");
Serial.print(blueFloat, DEC);
Serial.print(" bluePwr: ");
Serial.println(bluePwr, DEC);
// End debug
*/

}

if (lightMode == 2) {     // pulse fixed color

// display the colors
analogWrite(ledRed, redFloat);
analogWrite(ledGreen, greenFloat);
analogWrite(ledBlue, blueFloat);

// add delay here for speed control
delay(5);

// pulse down
if (pulse == 0) {
if (redFloat > 10) {
redFloat = redFloat - redKoff;
}
if (greenFloat > 10) {
greenFloat = greenFloat - greenKoff;
}
if (blueFloat > 10) {
blueFloat = blueFloat - blueKoff;
}

// If all xFloat match 10 get pulse up
if (byte(redFloat) <= 10) {
if (byte(greenFloat) <= 10) {
if (byte(blueFloat) <= 10) {
pulse = 1;
}
}
}
}
// Pulse up
if (pulse == 1) {
if (redFloat < redPulse) {
redFloat = redFloat + redKoff;
}
if (greenFloat < greenPulse) {
greenFloat = greenFloat + greenKoff;
}
if (blueFloat < bluePulse) {
blueFloat = blueFloat + blueKoff;
}
// If all Pwr match Pulse get pulse down

if (byte(redFloat) == redPulse) {
if (byte(greenFloat) == greenPulse) {
if (byte(blueFloat) == bluePulse) {
pulse = 0;
}
}
}
}
/*
// Debug
Serial.print("redFloat: ");
Serial.print(redFloat, DEC);
Serial.print(" redPulse: ");
Serial.print(redPulse, DEC);
Serial.print(" greenFloat: ");
Serial.print(greenFloat, DEC);
Serial.print(" greenPulse: ");
Serial.print(greenPulse, DEC);
Serial.print(" blueFloat: ");
Serial.print(blueFloat, DEC);
Serial.print(" bluePulse: ");
Serial.print(bluePulse, DEC);
Serial.print(" pulse: ");
Serial.println(pulse, DEC);
// End debug
*/
}

if (lightMode == 3) {  // randomsize colorNew and step colorPwr to it
if (redPwr > redNew) {
redPwr--;
}
if (redPwr < redNew) {
redPwr++;
}
if (greenPwr > greenNew) {
greenPwr--;
}
if (greenPwr < greenNew) {
greenPwr++;
}
if (bluePwr > blueNew) {
bluePwr--;
}
if (bluePwr < blueNew) {
bluePwr++;
}

// If all Pwr match New get new colors

if (redPwr == redNew) {
if (greenPwr == greenNew) {
if (bluePwr == blueNew) {
redNew = random(254);
greenNew = random(254);
blueNew = random(254);
}
}
}

// display the colors
analogWrite(ledRed, redPwr);
analogWrite(ledGreen, greenPwr);
analogWrite(ledBlue, bluePwr);
delay(20);
}
}

Monday, May 18, 2009

Backing up a system on Compact Flash

If you have a system on Compact Flash, for instance a digital picture frame. There's a really neat way to work on the system without having to write to the disk more than once, and still do alot of updates or other changes.

What you do is that you make a disk image of the Compact Flash media and then run that image in a machine emulator like Qemu for Linux and Windows or Q - [kju:] for OSX. Once the image is booted you can, work on the system and do all sorts of changes and when you're done all you have to do is write that image back to the media it came from. Ofcourse, if you're only doing minor changes this might be unecessary but for bigger system updates or just testing out new software before going live it's really nice.


How it's done
  1. Hook up your CF-card to your host system make sure the disk is not mounted, unmount it if necessary. But before doing that find out the path to the disk i.e: /dev/sdb, /dev/hdb or something like that.

  2. Then run dd to copy the whole disk to an image.
    Warning!
    The dd command can cause irreversible damage to your system. If you're not sure what you're doing, don't do it.

    Syntax: dd if=path_to_disk_to_backup of=outfile
    Where
    if stands for "Input file" and of for "Output file"

    For my system I use the following command:

    dd if=/dev/sdb of=lappy586.bin conv=noerror,sync
    The added option
    conv=noerror,sync makes sure that the copy will go on even if there are errors.
    The copy might take a while depending on the size of the image you try to copy.

  3. After the copy is done you can run the image with qemu like this:
    qemu -hda path_to_diskimage

    Qemu has alot of options, if things don't work out for you the first time have a look at the manpages and other documentation. I got lucky and had everything working without any need for extra configuration when running Qemu from Debian but not so lucky when trying to run the same image from Windows Vista.

  4. Finally, to write the backup back to disk just reverse the paths in the dd command:
    dd if=lappy586.bin of=/dev/sdb conv=noerror,sync

Friday, May 15, 2009

Useful parts to keep in stock

I thought I'd put together a little list of parts one might need when starting to fiddle with the Arduino and electronics in general. Why, well I would have liked one when I started out. It would have saved me alot on unnecessary shipping.
The list is partly my own, borrowing some from Tom Igoes Making Things Talk (a book I highly recommend) and a thread over at the Arduino forum I started.


Bread boards:
I thought I would only need one or two. By experience I know better and if you're like me, working on multiple projects, it's nice to have a few. It saves you the trouble of deconstructing and reconstructing half baked projects.

Jumper wire: For the breadboard. There are alot of different models. I've used solid core wires that were labeled for use with bread boards. I don't really care for them, however I don't have any other recommendations.

DC adaptor:
The Arduino uses a 2.1 mm center positive plug. However since you never know what you might need it's quite good to get one with interchangeable plugs in different sizes. Preferably with variable voltage aswell.

Connectors:
  1. DC 2.1 mm jack
    Useful when using a separate power source to your projects.
  2. DC 2.1 mm plug
    Useful when powering from battery.
  3. 9V battery connector
    Same reason as above but for the other end.
  4. Headers female and male.
    Alot of different uses. Especially when it comes to connecting wires to a breadboard.

Resistors:

  1. 100 Ohm
  2. 220 Ohm
  3. 470 Ohm
  4. 1 k Ohm
  5. 4.7 k Ohm
  6. 10 k Ohm
  7. 22 k Ohm
  8. 100 k Ohm
  9. 1 M Ohm

Variable
resistors:
  1. CdS resistor: 2-5 k Ohm
    Works as a light sensor if you hook it up to your micro controller of choice.
  2. NTC resistor: 4700 Ohm
    A.K.A. thermistor, like the above but senses heat instead.

  3. Potentiometer: 10 k Ohm
    Extremely useful when working with micro controllers.

Capacitors:

  1. 0.1 uF ceramic (sometimes labled as 100 nF)
  2. 1 uF electrolyte
  3. 47 uF electrolyte
  4. 10 uF electrolyte
  5. 100 uF electrolyte

ICs:
  1. H-bridge: L293D
    Useful for controlling DC motors and stepper motors. Allows you to switch polarity of the outputs.
  2. Darlington array: ULN2003
    Useful for controlling bigger loads such as stepper motors and LED-strips.

Transistors:

These are pretty much generic transistors that my supplier had in stock. If you can't find a specific transistor, just swap it out for one with similar specs.
  1. 2N2222A TO-92 NPN 40V 800mA 300MHz
  2. BC547B TO-92 NPN 45V 0.1A
  3. BC557B TO-92 PNP 50V 0.1A
  4. TIP120 TO-220 NPN Darlington 60V 5A
  5. IRF540 TO-220 N-ch MOSFET 100V 28A
  6. 2N3906 TO-92 PNP 40V 200mA

LEDs:
  1. Red 5 mm dim
    The most basic of LEDs.
  2. Red 5 mm clear super bright
    Together with the green and blue below it's what you need to do color mixing.
  3. Green 5 mm clear super bright
  4. Blue 5 mm clear super bright

Diodes:
  1. 1N4004 DO-41 400V 1A
  2. Zener BZX55C3V3 DO-35 3.3V

Voltage regulator:
  1. LM317T TO-220
    Variable voltage regulator, with two resistors you can get output voltages from 1.2 V to 37 V.

Wednesday, May 13, 2009

Automated notification of updated of external IP

Updated 2013-03-16

Script stopped working due to changes in html2text this was resolved by adding curl as a dependency.

For my DSL line I get a dynamic IP address. On the inside it's quite easy to find out what the external IP is, but on the outside damn near impossible to find out what the IP might have changed to.
To get around that problem I wrote a little script that checks the external IP and sends me an e-mail if there has been any changes.
Dependencies: curl, html2text and ssmtp.
Source formatted by formatmysourcecode.blogspot.com and available for download on github here.
# /bin/sh

# ipquery.sh
# IP checker and updater by Markus Ulfberg 2009-05-12 

# Variables
# Path where ip.txt resides or will be created if it doesen't exist already
DIR=youdirhere
# E-mail address, both from and to
ADDRESS=your@emailadress.here
# E-mail Subject
SUBJECT="New IP number"

# Check for ip.txt and create if it doesn't exist. 
if [ ! -s $DIR/ip.txt ] 
    then 
        # Create ip.txt
        echo "ip.txt doesn't exist. Creating ip.txt ..."
        echo "0.0.0.0" > $DIR/ip.txt
    else
        echo "ip.txt exists. Continuing ..."
fi

    # Get current IP
    currentIP=`curl http://checkip.dyndns.org | html2text | grep Current | awk '{print$4}'`
    echo "Current IP: $IP"

    # Read old IP from file
    oldIP=`cat $DIR/ip.txt`
    echo "Old IP: $oldIP"

    # Compare current and old IP
    if [ $oldIP != $currentIP ]
        then
            echo "IP has changed to: $currentIP"
            # Write new IP to ip.txt
            echo $currentIP > $DIR/ip.txt
            # Set e-mail content to new IP
            CONTENT=$currentIP
            # Send the e-mail notification
            echo -e "FROM:$ADDRESS\nTO:$ADDRESS\nSUBJECT:$SUBJECT\nContent-type:text/plain\n$CONTENT\n" | `/usr/sbin/ssmtp $ADDRESS`
        else
            echo "IP is still: $currentIP"
        fi
exit 0

Saturday, May 9, 2009

Converting the Toshiba 320CDS to a digital picture frame


Inspired by the many digital picture frame tutorials online I decided to make one myself.
First I jotted down some goals:
  1. Use a minimal amount of hardware and achive total silence.
    Solution: Strip away, speakers, battery, CD- and floppy drive. Instead of the hard drive use a Compact Flash card with a CF-IDE bridge.
  2. Use a minimal amount of wires.
    Solution: Use wireless networking.
  3. Design with a thin and flat formfactor.
    Solution: Mount everything between two sheets of transparent polycarbonate or acrylic glass (plexi glass).
  4. Run without keyboard or mouse.
    Solution: Remote control via ssh and/or vnc.
  5. Easy power up/down via the power button.
    Solution: Autologin in console and run X and other stuff from .bash_profile
    Problem: Since the Toshiba 320CDS uses an APM BIOS it cannot read events on the power button. In other words it cannot power down nicely using the power button. Any ACPI BIOS would be able to.
  6. Display a slide show of images.
    Solution: Use Feh, a very capable lightweight image viewer.
  7. Lenghten the life of the CF card by minimising disk writes/reads after boot.
    Solution: Do not use swap on the CF card, store images online, configure the system to report less.
  8. Display a Pong Clock.
    Solution: Use the pong xscreensaver.
    Problem: Won't run smoothly @233 MHz

Install a minimal debian installation
  1. Follow this excellent tutorial: wiki.dennyhalim.com/debian-minimal-desktop-installation

Additional Debian packages to use
  • Remote control with: ssh and x11vnc
  • Disable screen blanking in x: x11-xserver-utils
  • Wireless support: wpasupplicant wireless-tools
  • Image viewer: feh
  • Remove the mouse cursor: unclutter
  • Mount any ftp/sftp/... as local directory: curlftpfs

Set up wireless networking with 3Com Office Connect 11g PCMCIA

  1. Download the correct firmware from: prism54.org
  2. Install the packages wireless-tools and wpasupplicant
  3. Follow this excellent tutorial: http://ubuntuforums.org/showthread.php?t=318539
  4. To use multiple networks, use the following configuration to activate roaming mode:
    • Edit /etc/network/interfaces:
      allow-hotplug wlan0
      auto wlan0
      iface wlan0 inet manual # this has to be set to manual, dhcp configuration is done later
      wpa-driver wext
      wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf
      #id_str="[NAME_OF_NETWORK]" is the identifier that corresponds to the network configuration in wpa_supplicant.conf
      iface
      [NAME_OF_NETWORK] inet dhcp

    • Create a /etc/wpa_supplicant/wpa_supplicant.conf containing:
      ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
      network={
      ssid="[YOUR_SSID]"
      scan_ssid=1
      psk=[YOUR_PRESHARED_KEY_IN_HEX]
      key_mgmt=WPA-PSK
      pairwise=TKIP
      group=TKIP
      id_str="[NAME_OF_NETWORK]"
      }

Autologin in debian without starting x first

Follow one of the two examples here: www.debianadmin.com/how-to-auto-login-and-startx-without-a-display-manager-in-debian.html


Starting X after autologin:
  1. Add startx to .bash_profile

Set up and use curlftps after autoloing:

  1. Add your USER to the group fuse:
    sudo adduser USER fuse
    For this to activate you have to log in again, the easiest way to do this is:
    su USER
  2. Mount your remote directory by adding this line to .bash_profile:
    curlftpfs -r -o user=FTPUSERNAME:FTPPASSWORD FTPURL DIRTOMOUNTIN
  3. If you ever have to unmount the directory you can do this with umount as root or with fusermount -u as a user.

Start remove mouse cursor and start slideshow after starting x:

  1. Add these lines to ~/.config/openbox/autostart.sh:
    unclutter &
    feh --full-screen --randomize --auto-zoom --slideshow-delay 10 /home/makkan/images/ &

Disable screen blanking:

  1. First of all make sure the bios doesn't blank the screen.
  2. Add these two lines to .bash_profile:
    setterm -blank 0
    setterm -powersave off
  3. Edit /etc/console-tools/config
    Change this line: BLANK_TIME=30
    To: BLANK_TIME=0
  4. In your ~/.config/openbox/autostart.sh add these lines:
    xset -dpms &
    xset s off &
    xset s noblank &


Reduce disk writes

  1. If you have to use swap to get things working set it to minimal use by editing: /etc/sysctl.conf
    Add the line: vm.swappiness=0
  2. In /etc/sysctl.conf also activate laptop mode by adding the line:
    vm.laptop_mode=5
  3. Activate your new configuration by typing: sudo sysctl -p
  4. Rsyslog writes a MARK every 20:th minute, disable this by editing /etc/default/rsyslog and set:
    RSYSLOGD_OPTIONS="-m 0"
    Restart rsyslog with:
    sudo /etc/init.d/rsyslog restart
  5. Cron writes a report every hour/day/week and so forth. Disable this by editing:
    /etc/crontab