This Processing sketch takes data from the serial port, graphs it, and writes it to a text file with a time stamp if there’s a significant change in any of the incoming values. It expects five values between 0-255 in ASCII, separated by tabs, and ended by a carriage return and newline.
The text file it generates is tab-delimited, and can be read easily in a spreadsheet.
A Wiring/Arduino program to send data to this sketch follows at the end.
/*
Grapher Pro!
by Tom Igoe
This program takes raw bytes from the serial port at 9600 baud and graphs them.
It expects five ASCII-encoded decimal values from 0-255, tab-delimited, ended by a
newline and carriage return.
To change which of the five channels is being shown in the graph, type 0 through 5.
When any of the values changes by more than a set threshold, the program
writes the data to a text file called dataFile.txt, which can be found in the
sketch's directory.
Created 20 April 2005
Updated 15 October 2007
*/
import processing.serial.*;
Serial myPort; // The serial port
int arrayLength = 5; // number of values to expect
int[] sensorValues = new int[arrayLength]; // array to hold the incoming values
int hPosition = 0; // horizontal position on the graph
int displayChannel = 0; // which of the five readings is being displayed
String dataSet; // string holding the incoming data
int threshold = 50; // threshold for whether or not to write
// data to a file
void setup () {
size(400, 300); // window size
// List all the available serial ports
println(Serial.list());
// I know that the third port in the serial list on my mac
// is always my Keyspan adaptor, so I open Serial.list()[2].
// Open whatever port is the one you're using.
myPort = new Serial(this, Serial.list()[0], 9600);
// clear the serial buffer:
myPort.clear();
// don't generate a serialEvent() until you get a carriage return
myPort.bufferUntil('\r'); // ASCII 13
// create a font with the second font available to the system:
PFont myFont = createFont(PFont.list()[1], 24 );
textFont(myFont);
// make the graphics smooth:
smooth();
// set inital background:
background(0);
}
void draw () {
// if the value for the given channel is valid, graph it:
if (sensorValues[displayChannel] > 0 ) {
// print the name of the channel being graphed:
text("Channel: " + displayChannel, 30, 30);
// draw the graph:
graph(sensorValues[displayChannel]);
}
}
void serialEvent(Serial myPort) {
// read incoming data until you get a newline:
String serialString = myPort.readStringUntil('\n');
// if the read data is a real string, parse it:
if (serialString != null) {
// save the string in case you need to write it to a file:
dataSet = serialString;
// split it into substrings on the tab character:
String[] numbers = split(serialString, "\t");
// convert each subastring into an int
for (int i = 0; i < numbers.length; i++) {
// make sure you're only reading as many numbers as
// you can fit in the array:
if (numbers.length <= arrayLength) {
// trim off any whitespace from the substring:
numbers[i] = trim(numbers[i]);
// find the difference between the current value and the incoming value:
int diff = abs(sensorValues[i] - int(numbers[i]));
// if the difference exceeds the threshold, write the data to a file:
if (diff > threshold) {
writeToFile();
}
// save the new values in the array for use in graphing:
sensorValues[i] = int(numbers[i]);
}
}
}
}
void graph (int numberToGraph) {
// draw the line:
stroke(0,255,0);
line(hPosition, height, hPosition, height - numberToGraph);
// at the edge of the screen, go back to the beginning:
if (hPosition >= width) {
hPosition = 0;
// wipe the screen clean:
background(0);
}
else {
// advance the horizontal position on the graph:
hPosition++;
}
}
void keyPressed() {
// if the key pressed is "0" through "4"
if ((48 <= key) && (key <= 52)) {
// set the display channel accordingly
displayChannel = key - 48;
// wipe the screen:
background(0);
}
}
void writeToFile() {
// string for the new data you'll write to the file:
String[] newData = new String[1];
// add a time stamp:
newData[0] = timeStamp();
// add a tab:
newData[0] += "\t";
// add the latest data from the serial port:
newData[0] += trim(dataSet);
// get the existing data from the file:
String[] dataSoFar = loadStrings("dataFile.txt");
// if there's something there, dump it into an array
// and add the new data to it:
if (dataSoFar != null) {
// array needs to accommodate the old data and the new:
String[] dataToWrite = new String[dataSoFar.length + newData.length];
// dump the existing data from the file back in:
for (int s = 0; s < dataSoFar.length; s++) {
dataToWrite[s] = dataSoFar[s];
}
// append the new data:
for (int s = dataSoFar.length; s < dataToWrite.length; s++) {
dataToWrite[s] = newData[s-dataSoFar.length];
}
// dump the result back to the file:
saveStrings("dataFile.txt", dataToWrite);
}
// if there's no existing data:
else {
// add a header to the file:
newData[0] = "Time:\tSensor 1\tSensor 2\tSensor 3\tSensor 4\tSensor 5";
saveStrings("dataFile.txt", newData);
}
}
// make up a timeStamp string for writing data to the file:
String timeStamp() {
String now = hour()+ ":" + minute()+ ":" + second()+ " " +
month() + "/" + day() + "/" + year();
return now;
}
Here's a Wiring/Arduino program to send some serial data that the grapher can read:
/*
Read in five analog sensor values, send them out
as tab-delimited ASCCI-encoded decimal numbers,
ended by a carriage return and newline
created 15 Oct. 2007
*/
void setup() {
// iniaialize the serial port:
Serial.begin(9600);
}
void loop() {
// count from 0 to 4 (five channels):
for (int channel=0; channel <=4; channel++) {
// read an analog input, divide the result by 4
// to limit it to 0-255:
int sensorValue = analogRead(channel)/4;
// print it:
Serial.print(sensorValue, DEC);
// if it's not the last channel to be read,
// print a tab character:
if (channel < 4) {
Serial.print("\t");
}
// delay to let the analog-to-digital converter settle:
delay(8);
}
// once you've printed all the values,
// print a newline and carriage return:
Serial.println();
}