Sunday 28 February 2010

Using Processing and Arduino to display room temperature.

I've recently been getting into the Arduino project. For those who don't know, this is an open source project for prototyping electronic hardware. The Arduino board itself contains a microprocessor, which you can program in a version of c/c++ called Wiring; the board also has a USB port making it really easy to connect to your computer and get started. It also makes it easy to connect to the Arduino from other programs or hardware.

I've been playing around with my Arduino board for a while (just making LEDs blink and such) but today I did my first serious application and I'd like to share how I did this. Please remember, I'm just getting started with my Arduino, so this isn't a master class, but the application does everything that I set out to do (just maybe not the best way). I guess I should also point out that this isn't 'getting started with Arduino tutorial' even though it isn't too advanced; I have only maybe 10 hours experience but I'm going to assume that you've already got your Arduino development environment set up. If you haven't, not to worry, there are tons of excellent tutorials on the web, such as this one.

The hardware measures the ambient room temperature, my Arduino board takes the output from the sensor, transforms it to Centigrade, and pushes it down the USB serial port. The final part is a desktop program written the the Processing language, and this reads the value from the USB serial port and displays it in a window.

Hardware

For this project, you'll need the following items:
  • An Arduino board
  • A solderless breadboard
  • A temperature sensor (I used a LM35DT)
  • Wires to connect the various components
  • A USB cable

I got all these part in my initial Arduino box that I purchased from EarthShine Design.
Setting up the hardware is really easy. Plug the temperature sensor into your breadboard so that the three legs are on separate columns. The sensor needs 5 volts to power it. The front of the sensor has the name on and a little dot above leg number one which takes five volts so should be connected to the 5V pin on the Arduino. The middle leg should be connected to the GND pin, next to the 5V pin. The third leg should then be connected to the pin marked ANALOG IN 0, just right of the 5V and GND pins.

Wiring (version 1)

This is the first version of the software that I pushed to the Arduino. Once installed, it pushes the current temperature reading down the serial port every half a second. There are a few parts that it is worth pointing out.
  1. The loop function doesn't use a delay in order to read every half second. Instead, it stores when it last checked the temperature in the variable 'previousMillis' and doesn't check again until the current time is greater than 'previousMillis' plus 'interval' (which is 500 millisecond). I did it this way so that the code can be used alongside other code that also needs to loop; if delay() had been used, that other code would be delayed too.
  2. Wiring's version of sprintf doesn't support floats, so the code has to work out the temperature times 10 (so 30 degrees is stored as 300) and then in printTemperature() we put the period in manually by using the modulus operator.
  3. The last line in the setup() function sets the Arduino's reference voltage to 1.1 volts. This is done because the sensor module's third leg produces up to 1 volt; the sensor component measures from 0 to 100 degrees. 0 degrees is indicated by 0 volts and 100 degrees is indicated by 1 volt. Arduino automatically converts this voltage to a number between 0 and 1023.
const byte potPin = 0;         //pin that the temperature sensor is attached to
long temperature = 0;         //the current temperature
long previousMillis = 0;       // will store last time temperature was updated
const long interval = 500;     // interval at which to check the temperature

void setup() {
 Serial.begin(9600);
 Serial.flush();
 analogReference(INTERNAL);
}

void loop() {
 unsigned long currentMillis = millis();
 if(currentMillis - previousMillis > interval) {
  doTemperatureMeasurement();
  previousMillis = currentMillis; 
 }
}

void printTemperature(int value) {
 byte len = 4;
 if (value == 100) len = 5;
 char buffer[len];
 sprintf(buffer, "%3i.%1i", value / 10, value % 10);
 Serial.println(buffer);
}

int readAverage() {
 int span = 20;
 int aRead = 0;
 for (int i = 0; i < span; i++) {
  aRead = aRead+analogRead(potPin);
 }
 return aRead / span;
}

void doTemperatureMeasurement() {
 int aRead = readAverage();
 temperature = ((100*1.1*aRead)/1024)*10;
 printTemperature(temperature);
}
Once you have this code installed on your Arduino, you can open the serial monitor and should see the current temperature rattled back at you.

Processing (version 1)

With the Arduino now communicating the temperature down the USB serial cable I wanted to display this value on my monitor.
A project related to Arduino and Wiring is Processing. Actually, the Processing environment is virtually the same as the Wiring environment and the language is very similar too. Once you have the Processing environment installed and running, start a new project and save it (I called mine 'TempDisplay').

In order to display the temperature on the screen, we need a special font file that Processing uses to render text; a suitable file ships with Processing. From within Processing, click 'Add File...' on the Sketch menu. Open the directory where you installed Processing and then descend into 'examples/Basics/Typography/Words/data/'. Here you'll find a file called 'Ziggurat-HTF-Black-32.vlw'. Select this file.
import processing.serial.*;

String temperature = "0";
PFont font;
Serial port;

void setup() {
 String portName = Serial.list()[0];
 port = new Serial(this, portName, 9600);
 font = loadFont("Ziggurat-HTF-Black-32.vlw");
 textFont(font);
 textAlign(CENTER);
 size(200, 140);
 background(0);
 fill(0);
 smooth();
}

void draw() {
 if (port.available()>0) {
  delay(100);
  temperature = port.readString();
 }
 background(255); // Set background to dark gray
 text(temperature, width/2, height/2);
}
Before launching the application, you'll need to quit out of the Arduino environment, as the serial communication can't be opened by both programs at the same time.
The only line of code here that might cause a problem is the first line of the function setup(). It assumes that the USB serial connection is the first returned by Serial.list(). If yours isn't, you may have to play around with the number in the square brackets.

Wiring (version 2)

There were two things that I wasn't happy with in this solution.
  1. The Arduino communication is too chatty, by which I mean, the Arduino pushes the temperature down the wire even when it hasn't changed.
  2. When the Processing application starts up, there is a notable delay before it gets an update from the serial port during which time the temperature is displayed as zero.
The first problem is easy to fix. I simply changed the code for 'doTemperatureMeasurement()' to only update the temperature if it has changed since last time it was determined.
void doTemperatureMeasurement() {
 int aRead = readAverage();
 long newTemperature = ((100*1.1*aRead)/1024)*10;
 if (newTemperature != temperature) {
  temperature = newTemperature;
  printTemperature(temperature);
 }
}
This code change has an unfortunate side effect; The Processing application now has an even more noticeable delay when it starts up before it shows the real temperature. This is obviously because it is now waiting not for the half second to pass but for the temperature sensor to read that the room temperature has changed.

Wiring (version 3)

This is the final version of the Arduino software, so I've copied the whole thing again. The only change though is that the software listens (in the loop() function) for a message on the serial port. The message it is listening for is 'get temp' and when it hears this, it pushing the current temperature down the wire regardless of whether it has changed since last time.
Most of the code is to do with handling the character buffer but I've also refactored doTemperatureMeasurement(), separating out the code that determines the actual temperature into a new function, readTemperature(). This is used in the loop() function if the new message is heard on the wire.
const byte potPin = 0;         //pin that the temperature sensor is attached to
long temperature = 0;         //the current temperature
long previousMillis = 0;       // will store last time temperature was updated
const long interval = 500;     // interval at which to check the temperature
const byte maxSerialChars = 8;
char buffer[maxSerialChars];

void setup() {
 Serial.begin(9600);
 Serial.flush();
 analogReference(INTERNAL);
}

void loop() {
 if (Serial.available()>0) {
  delay(100);
  int numChars = Serial.available();
  int index = 0;
  if (numChars > maxSerialChars) {
   numChars = maxSerialChars;
  }
  while (numChars--) {
   buffer[index++] = Serial.read();
  }
  if (buffer[0] == 'g' && buffer[4] == 't') {
   printTemperature(readTemperature());
  }
  for (byte x=0; x<maxSerialChars; x++) {
   buffer[x] = '\0';
  }
 }

 unsigned long currentMillis = millis();
 if(currentMillis - previousMillis > interval) {
  doTemperatureMeasurement();
  previousMillis = currentMillis;
 }
}

void printTemperature(int value) {
 byte len = 4;
 if (value == 100) len = 5;
 char buffer[len];
 sprintf(buffer, "%3i.%1i", value / 10, value % 10);
 Serial.println(buffer);
}

int readAverage() {
 int span = 20;
 int aRead = 0;
 for (int i = 0; i < span; i++) {
  aRead = aRead+analogRead(potPin);
 }
 return aRead / span;
}

long readTemperature() {
 int aRead = readAverage();
 return ((100*1.1*aRead)/1024)*10;
}

void doTemperatureMeasurement() {
 long newTemperature = readTemperature();
 if (newTemperature != temperature) {
  temperature = newTemperature;
  printTemperature(temperature);
 }
}

Processing(version 2)

This this new code in place you can test the special message by opening the serial monitor and typing in 'get temp' but lets update the Processing application to make use of this new feature. Actually, we only need to add one extra line of code to the Processing application! On the line after we open the serial port and before we load the font, add the line:
port.write("get temp");
That's it. The Processing application will now load the temperature almost straight away. Not only will it not have to wait for the thermo sensor to register a temperature change but it also won't have to wait for the half second to pass as it did in the first version.

Next?

So, what's next? I'm not sure where I'll take the this thermo sensor device but a few thoughts occur to me. It would be nice to make the device perminent, perhaps by popping the microprocessor off the Arduino board (you can replace the chip fairly easily) and getting rid of the breadboard too.
It also might be interesting to investigate replacing the USB serial connection with perhaps a bluetooth connection or even a WiFi connection.