Part 1) Visualizing 3-Axis Accelerometer Readings in Processing by Christopher Hazlett
It's been a while since I've had the chance to do anything vaguely electronic. Sure, I've painted rooms in my house, installed ceiling fans, added insulation to my attic, but that's a far cry from programming in Wiring or Processing. So, thankfully, after getting my new workspace all put together, I got the chance to play with some of the parts I've had waiting in a few SparkFun boxes.
So I started playing around with a 3-Axis Accelerometer in the hopes of dreaming up some project or other. So I hooked it up to my Arduino and my Arduino to my computer and wrote a little Processing code to graph it all into pretty colors. As with all of my projects, the first step for me is understanding and since I didn't have much experience with Accelerometers a little crash course was in order. As it turns out, it's a fairly simple sensor to use (or collection of 3 sensors: x, y, z, I should say). Simply plug the VCC connector into the Arduino 3V pin (not the 5V pin. The ADXL3305 chip is only rated to 3.3V), the ground into ground and the x, y, and z pins into 0,1,2 analog pins. The code for the Arduino is simple:
The Arduino Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #define X_AXIS 0 #define Y_AXIS 1 #define Z_AXIS 2 void setup() { Serial.begin(9600); } void loop() { int x = analogRead(X_AXIS); int y = analogRead(Y_AXIS); int z = analogRead(Z_AXIS); Serial.print(x); Serial.print('|'); Serial.print(y); Serial.print(':'); Serial.println(z); } |
It takes the readings in and outputs them into a formatted string '[x]|[y]:[z]'. That's it. This is just for outputting data right now, so nothing special. It gets more interesting when we look at the processing.
The Processing Code in Action
The code that makes the sweet, sweet video above isn't necessarily complicated, but there may be a few things you haven't used in Processing before.
- map(value, low1, high1, low2, high2) - converts a value from one range into a corresponding value into another range.
- norm(value, low, high) - converts a value into a value from 0.0 to 1.0 based on the supplied range.
- pushMatrix() / popMatrix() - the pushMatrix() and popMatrix() methods allow you to apply rotation, translation, and other methods to a specific style. By issuing the pushMatrix() then calling the translate(), and rotateX, rotateY methods, you can then call popMatrix so those methods don't affect other elements being rendered by Processing.
The Processing Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | import processing.serial.*; import processing.opengl.*; Serial myPort; int baudRate = 9600; int lf = 10; PFont font; int[] xAxis; int[] yAxis; int[] zAxis; int currentX = 0; int currentY = 0; int currentZ = 0; //these value were determined by taking readings from a resting position int oneGSensorValue = 400; float oneGMillivolt = oneGSensorValue * 4.9; int totalReadings = 400; int readingPos = 0; // the reading position in the array void setup(){ smooth(); size(600, 300, OPENGL); font = createFont(PFont.list()[270], 24); smallFont(); xAxis = new int[totalReadings]; yAxis = new int[totalReadings]; zAxis = new int[totalReadings]; for (int i=0; i < totalReadings; i++){ xAxis[i] = oneGSensorValue; yAxis[i] = oneGSensorValue; zAxis[i] = oneGSensorValue; } myPort = new Serial(this, Serial.list()[0], baudRate); myPort.bufferUntil(lf); noLoop(); } void serialEvent(Serial p){ String inString; try{ inString = (myPort.readString()); currentX = xValue(inString); currentY = yValue(inString); currentZ = zValue(inString); xAxis = insertValueIntoArray(xAxis, currentX, readingPos, totalReadings); yAxis = insertValueIntoArray(yAxis, currentY, readingPos, totalReadings); zAxis = insertValueIntoArray(zAxis, currentZ, readingPos, totalReadings); readingPos = readingPos + 1; // increment the array position }catch(Exception e){ println(e); } redraw(); } void draw() { background(#FEFFFC); drawGraph(xAxis, 100, color(#519050), "X - Axis"); drawGraph(yAxis, 200, color(#708CDE), "Y - Axis"); drawGraph(zAxis, 300, color(#D38031), "Z - Axis"); draw3d(currentX, currentY, currentZ); } void drawGraph(int[] arrToDraw, int yPos, color graphColor, String name){ int arrLength = arrToDraw.length; stroke(graphColor); for (int x=0; x<arrLength - 1; x++) { float normalizedLine = norm(arrToDraw[x], 0.0, 700.0); float lineHeight = map(normalizedLine, 0.0, 1.0, 0.00, 85.0); line(x, yPos, x, yPos - int(lineHeight)); } pushStyle(); smallFont(); stroke(#FFFFFF); fill(#FFFFFF); String gString = nfc(gFromSensorValue(arrToDraw[arrLength - 2]), 2); text(name + " : " + gString + " Gs", 10, yPos - 10); popStyle(); } void draw3d(int currentX, int currentY, int currentZ){ float normalizedX = norm(currentX, 0.0, 700.0); float normalizedY = norm(currentY, 0.0, 700.0); float normalizedZ = norm(currentZ, 0.0, 700.0); float finalZ = map(normalizedZ, 0.0, 1.0, 300.00, 0.0); float finalY = map(normalizedY, 0.0, 1.0, -3.5, 3.5); float finalX = map(normalizedX, 0.0, 1.0, -3.5, 3.5); pushMatrix(); ambientLight(102, 102, 102); lightSpecular(204, 204, 204); directionalLight(102, 102, 102, -1, -1, -1); shininess(1.0); translate(500, finalZ); rotateY(finalY + 1.0); rotateZ(finalX); fill(#E2E8D5); noStroke(); fill(#B76F6F); float heightWidth = finalX * 1.8; box(65, 65, 50); popMatrix(); } int xValue(String inString){ int pipeIndex = inString.indexOf('|'); return int(inString.substring(0,pipeIndex)); } int yValue(String inString){ int pipeIndex = inString.indexOf('|'); int colonIndex = inString.indexOf(':'); return int(inString.substring(pipeIndex+1, colonIndex)); } int zValue(String inString){ int colonIndex = inString.indexOf(':'); return int(inString.substring(colonIndex + 1, inString.length() - 2)); } /* This little method creates a running tally of all the incoming sensor readings and then, when it reaches the end of the array, it pops the first one of the beginning and inserts a new value in at the end...thus keeping a running tally of the last 400 readings (it can be for any length array, that's just what it's set to for this project). This works a lot like an RRD graph where my inspiration came from. */ int[] insertValueIntoArray(int[] targetArray, int val, int pos, int maxLength){ if(pos > (maxLength-1)){ // if the pos == maxSize, shift the array to retain the original value int[] returnArray = subset(targetArray, 1, maxLength-1); returnArray = expand(returnArray, maxLength); returnArray[maxLength-2] = val; return returnArray; }else{ targetArray[pos] = val; return targetArray; } } /* This conversion will vary from project to project and if you're project is relying on battery power the reading may need to be adjusted to give you true one G as your battery power decreases. All of this is due to the output of the X,Y, and Z sensors and their coorelation to the incoming voltage at VCC Check out the specs for the ADXL335 (part of the break out board from Sparkfun.com) here: http://www.analog.com/en/sensors/inertial-sensors/adxl335/products/product.html */ float gFromSensorValue(int sensorValue){ //convert analog value into millivolts float mvValue = sensorValue * 4.9; return mvValue/oneGMillivolt; } void smallFont(){ textFont(font, 24); } void mediumFont(){ textFont(font, 30); } void largeFont(){ textFont(font, 40); } |
This is just the first step of a larger project to create a DIY radio control using an xBee and this 3-axis accelerometer.
Happy Coding.
- Chris