Force and position sensing with the XYZ digitizer pad
With the fundamentals covered and hardware assembled, it's coding time.
Here's an example Arduino sketch that reads an XYZ pad and outputs position + force readings in a serial terminal. You can grab the .zip below and upload to your MCU, or just take a
quick glance for reference if you'd prefer to write your own.
⬇ Download XYZPadTutorial.zip (Arduino Project)
/**********************************************************************************************************
* Project: XYZPadTutorial.ino
* By: Chris Wittmier @ Sensitronics LLC
* LastRev: 03/29/2015
* Description: Demo Firmware for FSR-based XYZPad sensors
**********************************************************************************************************/
#include <Arduino.h>
#include "xyzdefines.h"
int history[AXIS_COUNT][AVERAGE_COUNT];
int average[AXIS_COUNT];
float offset[AXIS_COUNT];
float scale[AXIS_COUNT];
int history_index;
void setup()
{
Serial.begin(115200);
pinMode(PIN_XPLUS, INPUT);
pinMode(PIN_XMINUS, INPUT);
pinMode(PIN_YPLUS, INPUT);
pinMode(PIN_YMINUS, INPUT);
for(int i = 0; i < AXIS_COUNT; i ++)
{
for(int k = 0; k < AVERAGE_COUNT; k ++) { history[i][k] = 0; }
average[i] = 0;
offset[i] = 0;
scale[i] = 1;
}
history_index = 0;
}
void loop()
{
/* Read X-position */
pinMode(PIN_YPLUS, INPUT);
pinMode(PIN_YMINUS, INPUT);
pinMode(PIN_XPLUS, OUTPUT);
pinMode(PIN_XMINUS, OUTPUT);
digitalWrite(PIN_XMINUS, LOW);
digitalWrite(PIN_XPLUS, HIGH);
analogRead(PIN_YPLUS);
int x_reading = analogRead(PIN_YPLUS);
digitalWrite(PIN_XPLUS, LOW);
/* Read Y-position */
pinMode(PIN_XPLUS, INPUT);
pinMode(PIN_XMINUS, INPUT);
pinMode(PIN_YPLUS, OUTPUT);
pinMode(PIN_YMINUS, OUTPUT);
digitalWrite(PIN_YMINUS, LOW);
digitalWrite(PIN_YPLUS, HIGH);
analogRead(PIN_XPLUS);
int y_reading = analogRead(PIN_XPLUS);
digitalWrite(PIN_YPLUS, LOW);
/* Read Force */
pinMode(PIN_XPLUS, INPUT);
pinMode(PIN_YPLUS, INPUT);
pinMode(PIN_XMINUS, OUTPUT);
pinMode(PIN_YMINUS, OUTPUT);
digitalWrite(PIN_XMINUS, LOW);
digitalWrite(PIN_YMINUS, HIGH);
analogRead(PIN_XPLUS);
int x_plane_reading = analogRead(PIN_XPLUS);
analogRead(PIN_YPLUS);
int y_plane_reading = analogRead(PIN_YPLUS);
digitalWrite(PIN_YMINUS, LOW);
int z_reading = y_plane_reading - x_plane_reading;
/* Idle */
pinMode(PIN_XPLUS, INPUT);
pinMode(PIN_XMINUS, INPUT);
pinMode(PIN_YPLUS, INPUT);
pinMode(PIN_YMINUS, INPUT);
if(FLIP_X_AXIS) { x_reading = 1024 - x_reading; }
if(FLIP_Y_AXIS) { y_reading = 1024 - y_reading; }
if(FLIP_Z_AXIS) { z_reading = 1024 - z_reading; }
averageAll(x_reading, y_reading, z_reading);
#ifdef TERMINAL_OUTPUT
if(average[Z_AXIS] >= Z_CONTACT_THRESHOLD)
{
Serial.print("X: "); printFixed(average[X_AXIS], 4);
Serial.print(" Y: "); printFixed(average[Y_AXIS], 4);
Serial.print(" Z: "); printFixed(average[Z_AXIS], 4);
Serial.println();
}
#endif
#ifdef PROCESSING_OUTPUT
procReport(average[X_AXIS], average[Y_AXIS], average[Z_AXIS]);
#endif
delay(DELAY_AFTER_SEND);
}
void averageAll(int xvalue, int yvalue, int zvalue)
{
unsigned long x_sum = 0, y_sum = 0, z_sum = 0;
history[X_AXIS][history_index] = xvalue;
history[Y_AXIS][history_index] = yvalue;
history[Z_AXIS][history_index] = zvalue;
for(int i = 0; i < AVERAGE_COUNT; i ++)
{
x_sum += history[X_AXIS][i];
y_sum += history[Y_AXIS][i];
z_sum += history[Z_AXIS][i];
}
average[X_AXIS] = (int)(x_sum / AVERAGE_COUNT);
average[Y_AXIS] = (int)(y_sum / AVERAGE_COUNT);
average[Z_AXIS] = (int)(z_sum / AVERAGE_COUNT);
history_index = (history_index < (AVERAGE_COUNT - 1)) ? history_index + 1 : 0;
}
void procReport(int xvalue, int yvalue, int zvalue)
{
float temp;
byte buff[8];
temp = ((float) xvalue - offset[X_AXIS]) * scale[X_AXIS];
xvalue = (temp > 1023) ? 1023 : ((temp < 0) ? 0 : (int) temp);
temp = ((float)yvalue - offset[Y_AXIS]) * scale[Y_AXIS];
yvalue = (temp > 1023) ? 1023 : ((temp < 0) ? 0 : (int) temp);
if(zvalue >= Z_CONTACT_THRESHOLD)
{
buff[0] = highByte(xvalue); buff[1] = lowByte(xvalue);
buff[2] = highByte(yvalue); buff[3] = lowByte(yvalue);
buff[4] = highByte(zvalue); buff[5] = lowByte(zvalue);
buff[6] = 0xFF; buff[7] = 0xFF;
Serial.write(buff, 8);
}
}
void printFixed(int value, int places)
{
int temp = 1;
if(value < 0) { places -= 1; }
for(int i = 1; i < places; i++)
{
temp *= 10;
if(value < temp) { Serial.print(" "); }
}
Serial.print(value);
}
And here's the header file for that sketch (it's also included in the project .zip file posted above). The readily tweak-able parameters are in here.
/**********************************************************************************************************
* File: xyzdefines.h
* By: Chris Wittmier @ Sensitronics LLC
* LastRev: 03/29/2015
**********************************************************************************************************/
#ifndef xyzdefines_h
#define xyzdefines_h
#define TERMINAL_OUTPUT 1
//#define PROCESSING_OUTPUT 1 //uncomment for Processing graphical output
#define PIN_XPLUS A0
#define PIN_XMINUS A1
#define PIN_YPLUS A2
#define PIN_YMINUS A3
#define Z_CONTACT_THRESHOLD 5
#define DELAY_AFTER_SEND 1
#define AVERAGE_COUNT 5
#define FLIP_X_AXIS 0
#define FLIP_Y_AXIS 0
#define FLIP_Z_AXIS 1
#define AXIS_COUNT 3
#define X_AXIS 0
#define Y_AXIS 1
#define Z_AXIS 2
#endif
If you've uploaded the example firmware and opened a terminal window at 115200kbps you should see... a blank terminal.
This is because there's a threshold in the code which blocks output until force
is detected. Without a threshold, you'd see a stream of false position data; the ADC inputs are more or less floating when the sensor layers are not pressed together.
So give it a press, and you should see something like the screenshot at right.
If you've oriented things similarly, pressing the top left corner should show low X and Y values, which gradually increase as you move right and down, respectively.
If not, give all the hardware connections a double check to verify everything's in order.
If everything went successfully, you now have a functional XYZ pad and some terminal output. We could call it a day, but let's (optionally) take it one step further!
In the final section, we'll add graphical feedback with a simple but rather neat demo GUI, using Processing...