Sunday, September 25, 2011

And if your Xbee shield is behaving strangely...

If you are combining the CAN-Bus and Xbee shields from SparkFun, it can be very tempting to plonk the EM406 GPS atop the nice flat prototyping area on the Xbee shield. It's a good fit there! But if stuff suddenly and inexplicably stops working, check that the GPS chassis isn't bridging the terminals on the Xbee shield's RESET switch. I felt like a real idiot :-(

Arduino: combining CAN-bus and Xbee shields


A little box of Arduino, originally uploaded by indigoid.

So I created a simple little sketch, based on the TimeGPS sample that comes with the TinyGPS library, that waits for a "p" over the Xbee interface and upon receiving it, sends back the date, time, latitude and longitude data from the GPS. The CAN-bus shield uses pins 4 and 5 for the GPS serial interface. I found that the Xbee modules I have (as supplied in the SparkFun Xbee retail kit) have rather shorter range than I'd hoped for, but at least they work and I am learning about their usage. Here's the sketch:


#include <TinyGPS.h>        //http://arduiniana.org/libraries/TinyGPS/
#include <NewSoftSerial.h>  //http://arduiniana.org/libraries/newsoftserial/
// GPS and NewSoftSerial libraries are the work of Mikal Hart

TinyGPS gps; 
NewSoftSerial serial_gps =  NewSoftSerial(4, 5);  // receive on pin 3

void setup()
{
  Serial.begin(9600);
  serial_gps.begin(4800);
  Serial.println("setup...");
}

void dump_lat_long(float flat, float flong) {
  Serial.print("lat : "); Serial.println(flat);
  Serial.print("long: "); Serial.println(flong);
}

#define pdec(x) { Serial.print(x,DEC); }
#define p(x) { Serial.print(x); }
#define s() p(" ")
#define d() p("-")
#define c() p(":")
void dump_datetime() {
  int year;
  byte month, day, hour, minutes, second, hundredths;
  unsigned long fix_age;
  gps.crack_datetime(&year, &month, &day, &hour, &minutes, &second, &hundredths, &fix_age);
  pdec(year); d(); pdec(month); d(); pdec(day);
  s();
  pdec(hour); c(); pdec(minutes); c(); pdec(second); p("."); pdec(hundredths);
  Serial.println();
}

void loop() {
  float flat, flon;
  unsigned long fix_age;
  String msg;
  byte havedata = 1;
  byte polled = 0;
  while (serial_gps.available() && !polled) {
    if(gps.encode(serial_gps.read())) {
      // returns +- latitude/longitude in degrees
      gps.f_get_position(&flat, &flon, &fix_age);
      if (fix_age == TinyGPS::GPS_INVALID_AGE) {
        msg = "No fix detected";
        havedata = 0;
      } else if (fix_age > 5000)
        msg = "Warning: possible stale data!";
      else
        msg = "Data is current.";
      polled = 1;
      if (Serial.available() && Serial.read() == 'p') {
        Serial.println(msg);
        if (havedata) {
          dump_datetime();
          dump_lat_long(flat,flon);
        }
        delay(100);
      }
    }
  }
}

I also wrote a small Perl script that (via Device::SerialPort from CPAN) interrogates the Arduino every 5 seconds or so, using a USB-attached Xbee Explorer. For Linux you will need to change the serial port device filename to /dev/ttyS0 or similar. No idea about Windows, sorry. Again, I started out with some sample code (this time from the Device::SerialPort distribution) and hacked most of it off. Code follows:


#!/usr/bin/perl

use strict;
use warnings;
use Device::SerialPort;

my $file = "/dev/tty.usbserial-A700fbpg";
my $ob = Device::SerialPort->new ($file) || die "Can't open $file: $!";

$ob->baudrate(9600)     || die "fail setting baudrate";
$ob->parity("none")     || die "fail setting parity";
$ob->databits(8)        || die "fail setting databits";
$ob->stopbits(1)        || die "fail setting stopbits";
$ob->handshake("none")  || die "fail setting handshake";
$ob->write_settings || die "no settings";
$ob->error_msg(1);              # use built-in error messages
$ob->user_msg(1);

while(1) {
  $ob->write("p");
  print $ob->input;
  sleep 5;
}

Monday, September 12, 2011

stairwell sensor light: Arduino!

So I wanted a small light at the bottom of the short set of stairs that lead from the door of my apartment into the main living area. I've been learning about Arduino lately, so I figured this might be a practical experiment. I decided to use a reed switch to sense when the door had been opened.

After getting frustrated at the reed switch functioning just fine when closed (Arduino digitalRead() returning HIGH) but bouncing around randomly when it should have been staying open, I did some Googling and found that I should have used a pullup resistor to coax the current in the right direction. I don't pretend to understand why this works, but I intend to find out.

Anyway, I used a circuit based very much on this one, plus of course an RGB LED to do the actual lighting. The next step is to grab a couple more RGB LEDs (I only had one on hand that had been supplied in the Sparkfun Inventors Kit) and make it brighter.

Learning about and experimenting with Arduino has been a lot of fun so far. Am working on a larger project for the bike. Along the way I've dramatically improved my soldering skills, though they are still pretty terrible!

#include <Time.h>

// pins
const int reedswitch = 2;
const int red = 9;
const int green = 10;
const int blue = 11;

// door states
const int DOOR_OPEN = 0;
const int DOOR_CLOSED = 1;

// minimum time the courtesy light will stay on for (seconds)
const int MIN_OPENTIME = 10;

///////////////////////////////////////////

int door = DOOR_CLOSED;
int last_open_at = 0;

void setup() {
  pinMode(reedswitch, INPUT);
  setTime(0);
}

void rgbled(int r, int g, int b) {
  analogWrite(red,r);
  analogWrite(green,g);
  analogWrite(blue,b);
}

void loop() {
  int reedswitch_state = digitalRead(reedswitch);
  if (reedswitch_state == HIGH && now() >= last_open_at + MIN_OPENTIME) {
    door = DOOR_CLOSED;
  } else if (reedswitch_state == LOW) {
    last_open_at = now();
    door = DOOR_OPEN;
  } else {
    // no change in state, do nothing
  }
  if (door == DOOR_OPEN) {
    rgbled(255,255,255);
  } else {
    rgbled(0,0,0);
  }
  delay(500);
}