DIY Sensor Touchpad

by Markus Opitz in Circuits > Arduino

1257 Views, 10 Favorites, 0 Comments

DIY Sensor Touchpad

TouchPad-Titel.jpg
14-pinout-live1.jpg

Sensors are fascinating; they are the eyes, ears, nose, and sense of touch of our computers and microcontrollers. But a homemade sensor—now that’s something!

As a teenager, I built myself an electronic “piano” with a keyboard made of pencil graphite that detects the different resistances. Basically, it was a homemade potentiometer attached to a sound generator.

This touchpad itself is very cheap: 0.15€/sensor (1g).


Maybe not very precisely, but let's give it a try.

Supplies

I built a DIY sensor touch pad #shorts #diy #arduino #3dprinting
  1. 3D print conductive Filament (PLA) ~30€/200g
  2. Inkjet Film (Printable film for overhead projectors, heat resistant)
  3. XIAO ESP32-C6 (or any other compatible Arduino microcontroller)
  4. OLED display 128 x 64
  5. wires and soldering iron

Layout of the Conductive Traces

01-Entwurf1.jpg
02-Entwurf2.jpg
04-Druck.jpg

There are several ways to create a sensor grid. Basically, you need two wires placed side by side that are short-circuited at specific points. You can use multiple layers and different materials. But I want to try using a grid printed entirely in 3D. These sensors work on the same principle, namely by establishing an electrical connection between two conductors through skin contact.

All we need to do now is determine exactly where on the array this contact occurs, so that we can assign coordinates.

Design 1 is what you see on the following images. A few bridges of filament must be installed manually. Design 2 is only 3D printed but with longer wires.

Width & Height of the Conductor Traces

05-Druckversuche.jpg

I’ve set the track width to 1mm so that the grid doesn’t get too big. Don’t forget the spacing!

After conducting several tests, I have found that the ideal layer thickness is 0.4 mm. 0.2 mm could also work, but the resistance of the tracks is higher.

Print Settings

05-Druck-Nix.jpg
06-Druck-ok.jpg

I had to carry out a few tests here as well. The filament obviously needs to be printed on a base. I tried using wood. It’s sturdy, but unfortunately too porous and ‘absorbs’ some of the filament, which makes the structure uneven.

Heat-resistant printing film for overhead projectors has proved useful. The substrate is reasonably sturdy yet flexible at the same time. The film is attached to the printing plate using adhesive tape. You’ll have to try it out for yourself to see whether you need to add an offset to the Z-coordinate. (I added 0.1 mm)

Attention: Set the plate temperature down to warm (30°C in my case)


Temperature: 250°C

Plate: 30°C

Speed: 25 mm/s

Offset (z): 0.1 mm (for the film)


Measuring the Resistances

pinout2.jpg

Note down the resistance values in the top-left and bottom-right corners. Enter these values into the program under resMin and resMax.

The microcontroller measures the resistance and maps the value to a coordinate on the sensor, which it then displays on the OLED display.


Interpretation of the Data As Specific Coordinates

18-working3.JPG

The difference between the starting and ending values is divided (here) into 7 lines, and each measured value can then be easily assigned to a line (y-value).

Within a line, the measured value is assigned an x-value ranging from 1 to 127 (display width).

Using the map command, we map each resistance value in a row to a column value

I have set the point on the display so that it remains visible until a new coordinate is entered. Of course, the point can also be deleted if there is ‘no signal’

The software is measuring the resistance every 10ms and is assigning the data to a specific coordinate. We have .... lines in our grid, starting from the left upper corner like our OLED display does.


Complete Software & Transfer of Coordinates to the Display

16-working1.JPG
// DIY sensor touchpad
// Markus Opitz 2026
// instructables.com


#include <Arduino.h>

// measure ***********************
const int ADC_PIN = A0;
const float VCC = 3.3;
const float R_REF = 1000000.0; // 1 MOhm reference resistor
const int N_SAMPLES = 64;

//coordinates **************
float resMin = 2.0;
float resMax = 6.0;
float resLine;
float resValue;
int x, y;
float resX;

// OLED ******************
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

#define OLED_ADDR 0x3D // I2C-address (mostly 0x3C)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);


float mapFloat(float x, float in_min, float in_max, float out_min, float out_max) { //for using float values in a map funktion
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}


void setup() {
Serial.begin(115200);
delay(500);

resLine = (resMax-resMin)/7; // 7 lines --> a 9 lines on OLED display
Serial.println(resLine, 3); delay(2000);


pinMode(ADC_PIN, INPUT);
analogReadResolution(12);
analogSetPinAttenuation(ADC_PIN, ADC_11db); // großer Messbereich bis nahe 3.3 V

if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.println(F("SSD1306 not found"));
for (;;);
}
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(WHITE);
display.setCursor(30, 25);
display.println(F("ready"));
display.display();
delay(1000);
}

void loop() {
uint32_t sum = 0;

for (int i = 0; i < N_SAMPLES; i++) {
sum += analogReadMilliVolts(ADC_PIN);
delayMicroseconds(200);
}

float vOut = (sum / (float)N_SAMPLES) / 1000.0;

float rX;
if (vOut <= 0.001) {
rX = 0.0;
} else if (vOut >= VCC - 0.001) {
rX = INFINITY;
} else {
rX = R_REF * vOut / (VCC - vOut);
}

Serial.print("Vout = ");
Serial.print(vOut, 3);
Serial.print(" V, Rx = ");
resValue = rX / 1000000.0;

if (isinf(rX)) {
Serial.println("> Messbereich / sehr hochohmig");
}
else if (resValue > 75) Serial.println(" - ");
else {
Serial.print(resValue, 3);
Serial.print(" MOhm ");
checkXY();
Serial.print(x); Serial.print(" "); Serial.println(y);
displayDot();
}

delay(500);
}

void checkXY() {
if ((resValue > resMin) && (resValue < (resMin + resLine))) {
y=5;
//resX = resValue;
//x = mapFloat(resX, resMin,(resMin+ resLine) , 1, 128);
x = (int)mapFloat(resValue, resMin, (resMin+ resLine), 1, 126);
//Serial.println("line 1");
}
if ((resValue > resMin + resLine) && (resValue < (resMin + 2 * resLine))) {
y=14;
resX = resValue;
//x = mapFloat(resX, (resMin + 1*resLine),(resMin+ 2*resLine) , 128, 1);
x = (int)mapFloat(resValue, (resMin + 1*resLine), (resMin+ 2*resLine), 126, 1);
//Serial.println("line 2");
}
if ((resValue > resMin + 2 * resLine) && (resValue < (resMin + 3 * resLine))) {
y=23;
resX = resValue;
//x = mapFloat(resX, (resMin + 2*resLine),(resMin+ 3*resLine) , 1, 128);
x = (int)mapFloat(resValue, (resMin + 2*resLine), (resMin+ 3*resLine), 1, 126);
//Serial.println("line 3");
}
if ((resValue > resMin + 3 * resLine) && (resValue < (resMin + 4 * resLine))) {
y=32;
resX = resValue;
//x = mapFloat(resX, (resMin + 3*resLine),(resMin+ 4*resLine) , 128, 1);
x = (int)mapFloat(resValue, (resMin + 3*resLine), (resMin+ 4*resLine), 126, 1);
//Serial.println("line 4");
}
if ((resValue > resMin + 4 * resLine) && (resValue < (resMin + 5 * resLine))) {
y=41;
resX = resValue;
//x = mapFloat(resX, (resMin + 4*resLine),(resMin+ 5*resLine) , 1, 128);
x = (int)mapFloat(resValue, (resMin + 4*resLine), (resMin+ 5*resLine), 1, 126);
//Serial.println("line 5");
}
if ((resValue > resMin + 5 * resLine) && (resValue < (resMin + 6 * resLine))) {
y=50;
resX = resValue;
//x = mapFloat(resX, (resMin + 5*resLine),(resMin+ 6*resLine) , 128, 1);
x = (int)mapFloat(resValue, (resMin + 5*resLine), (resMin+ 6*resLine), 126, 1);
//Serial.println("line 6");
}
if ((resValue > resMin + 6 * resLine) && (resValue < (resMax))) {
y=59;
resX = resValue;
//x = mapFloat(resX, (resMin+6*resLine),(resMax) , 1, 128);
x = (int)mapFloat(resValue, (resMin + 6*resLine), resMax, 1, 126);
//Serial.println("line 7");
}
}

void displayDot() {
display.clearDisplay();
display.fillCircle(x, y, 2, SSD1306_WHITE);
display.display();
delay(10);
}

Downloads