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(); }