XYZ Pad

Force and position sensing with the XYZ digitizer pad

Part 4: Arduino Code

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)

XYZPadTutorial.ino

/**********************************************************************************************************
* 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.

xyzdefines.h

/**********************************************************************************************************
* 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

Sample Output


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.


Graphics Would Be Nice


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...

← PreviousPage 4 of 5Next →