Wearables and E-textiles
Integrate the Touch Board into garments and body-worn interfaces using conductive fabric, thread, and LiPo power.
Wearing electronics transforms interaction from something you do at a desk into something that lives in your body and movement. The Bare Conductive Touch Board is well suited to wearable projects: it runs at 3.3 V from a LiPo cell, its electrodes accept anything conductive, and its VS1053 MP3 decoder means you can trigger audio directly without a laptop. This tutorial covers materials, power, construction techniques, and the firmware and code adjustments needed for body-worn contexts.
Power: LiPo Batteries
The Touch Board runs from its onboard 5 V regulator when USB-powered, but it also accepts a single-cell LiPo battery (3.7 V nominal, 4.2 V fully charged) via the JST-PH 2-pin connector on the underside of the board.
Choosing a Battery
| Capacity | Weight | Run time (typical) |
|---|---|---|
| 100 mAh | 3 g | ~1 hour |
| 500 mAh | 12 g | ~5 hours |
| 1000 mAh | 25 g | ~10 hours |
| 2000 mAh | 50 g | ~20 hours |
The Touch Board draws approximately 80–120 mA when actively playing MP3 audio. In idle (no playback, all electrodes scanning) it draws around 40 mA.
Charging
Connect the Touch Board via micro USB while the LiPo is attached — the onboard MCP73831 charge IC charges at 500 mA. A full charge from flat takes about 1–4 hours depending on capacity.
Battery Safety Checklist
- Never puncture, bend, or compress a LiPo cell
- Do not expose to temperatures above 60°C (avoid leaving in direct sunlight)
- Use a JST-PH connector with the correct polarity — red wire to +, black to GND
- Include a resettable fuse (PTC) in the positive line if the battery will be sewn into a garment that might be machine-washed
- If the battery puffs (swells), discharge it fully and dispose of it at a battery recycling point
Conductive Materials
Conductive Thread
Stainless-steel conductive thread (e.g. Adafruit Stainless Thin, LessEMF) can be sewn directly into fabric with a standard needle. Use it to route electrical connections between the Touch Board and fabric electrodes.
Resistance matters. Most conductive threads have 10–100 Ω/cm. For electrode connections keep runs short (under 30 cm) or the added resistance will lower the MPR121’s sensitivity. Test with a multimeter.
Avoiding shorts. If two conductive thread traces cross, insulate one with a patch of iron-on interfacing. Plan your routing before you sew, just as you would a PCB.
Conductive Fabric
Fabric electrodes behave like any large-area electrode — they produce a strong, stable capacitive signal. Suitable materials:
- Shieldit Super (Adafruit): 12 Ω/sq, iron-on adhesive on one side, easy to cut into shapes
- Less EMF stretch knit: conformable to curves, suitable for shaped electrode patches
- DIY aluminium foil + iron-on hem tape: cheap, works in a pinch but tears easily
Electric Paint
Bare Conductive’s own Electric Paint can be applied to fabric with a brush, screen, or stencil. It remains flexible when dry. Wash resistance is limited — for garments that will be washed, seal with a stretch fabric glue.
Wearable Electrode Patterns
Knuckle Pads
Sew or glue small conductive fabric squares (2 × 2 cm) to the back of a glove, one per knuckle or finger segment. Connect each to a Touch Board electrode with conductive thread routed along the glove seams.
Electrode: E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 E10 E11
Position: Thumb IF IF2 MF MF2 RF RF2 LF LF2 Palm -- --
(IF = Index Finger, MF = Middle, RF = Ring, LF = Little)
Chest Straps
A strip of conductive fabric sewn inside a snug-fitting shirt creates a single large electrode. This electrode responds to both touch from hands and the wearer’s own respiratory movement (as the chest expands and contracts, the electrode-to-electrode capacitance changes slightly). Use this signal as a breathing sensor:
// Breathing detection via slow baseline drift
int prev = 0;
void loop() {
int raw = MPR121.getBaselineData(0) - MPR121.getFilteredData(0);
int delta = raw - prev;
prev = raw;
// Positive delta = exhale (fabric relaxing), negative = inhale
Serial.print("BR:"); Serial.println(delta);
delay(50); // 20 Hz is enough for breathing
}
Spine Sequencer
Twelve conductive patches sewn along the spine of a jacket from neck to waist act as a proximity strip. As a hand brushes down the wearer’s back, the proximity centroid moves from electrode 0 to electrode 11, creating a top-to-bottom sweep gesture.
Firmware for Wearables
Standalone Audio Mode
In wearable contexts you often want the Touch Board to work completely standalone: touch → sound, no laptop required. The default Touch Board firmware already does this, but you may want to customise sound assignment.
The Touch Board firmware reads file names from the SD card. Sound files should be named TRACK000.MP3 through TRACK011.MP3 and placed in the root of the SD card. Electrode N plays TRACKNNN.MP3.
To customise the map (e.g. play the same sound on multiple electrodes, or skip some):
// Custom sound map (index = electrode, value = track number)
int soundMap[12] = {0, 0, 1, 1, 2, 2, 3, 3, 4, 5, 6, 7};
void playTrack(int electrode) {
int track = soundMap[electrode];
char filename[16];
sprintf(filename, "/TRACK%03d.MP3", track);
MP3player.stopTrack();
MP3player.playMP3(filename);
}
Low-power Sleep
When the garment is worn but not actively used, the Touch Board wastes power scanning electrodes. Add a sleep timeout using the ATmega32U4’s watchdog timer:
#include <avr/sleep.h>
#include <avr/wdt.h>
unsigned long lastActivity = 0;
const unsigned long SLEEP_TIMEOUT = 30000; // 30 seconds
void loop() {
if (MPR121.touchStatusChanged()) {
MPR121.updateTouchData();
lastActivity = millis();
for (int i = 0; i < 12; i++) {
if (MPR121.isNewTouch(i)) playTrack(i);
}
}
if (millis() - lastActivity > SLEEP_TIMEOUT) {
enterSleep();
}
}
void enterSleep() {
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
// MPR121 interrupt will wake the MCU
sleep_cpu();
sleep_disable();
lastActivity = millis();
}
Note: in deep sleep the MPR121 still scans and can assert its IRQ line to wake the ATmega. Wire the MPR121 IRQ pin to an interrupt-capable pin (D0 or D1 on the Touch Board) and configure it with attachInterrupt.
Garment Construction Tips
Isolation and Shielding
The human body is electrically noisy. Signals from nearby mains wiring, fluorescent lights, and other electronics couple into large fabric electrodes. Keep electrode traces away from:
- Motor drivers and solenoids (use separate power rails)
- USB data lines (route conductive thread at 90° to USB cables where they cross)
- Skin contact — if the electrode touches bare skin directly the MPR121 may not detect additional touch events reliably. Add a thin layer of fabric between electrode and skin.
Strain Relief
Conductive thread breaks at flex points. At the point where thread connects to the Touch Board, add a blob of fabric glue to mechanically anchor the thread before it reaches the solder pad. Better: use a snap stud connector — sew the snap to the fabric and use a short wire to connect it to the board.
Washability
For garments that will be washed:
- Enclose the Touch Board and battery in a waterproof zip pouch (e.g. silicone phone pouch) or remove them before washing
- Seal all conductive thread connections with fabric-compatible insulating varnish
- Test the electrical connections after every third wash — resistance tends to increase as the metallic coating on conductive thread oxidises
- Electric Paint electrodes on fabric are not wash-resistant without a sealant layer
p5.js Wearable Interface
This sketch receives touch data from the Touch Board over Bluetooth (using a HC-05 or HM-10 module on the Arduino side) and displays a real-time body diagram with active touch zones highlighted.
For the wired USB equivalent simply use the Web Serial API from previous tutorials. Below is the visualisation logic, which works with either transport:
let touchStates = new Array(12).fill(false);
let breathData = [];
const MAX_BREATH = 200;
// --- Body diagram layout (12 electrode positions on a stylised figure) ---
let electrodePositions; // set in setup() based on canvas size
function setup() {
createCanvas(600, 800);
electrodePositions = [
// Left hand
{ x: 0.18, y: 0.45, label: 'LH1' },
{ x: 0.15, y: 0.50, label: 'LH2' },
{ x: 0.13, y: 0.55, label: 'LH3' },
{ x: 0.16, y: 0.60, label: 'LH4' },
// Right hand
{ x: 0.82, y: 0.45, label: 'RH1' },
{ x: 0.85, y: 0.50, label: 'RH2' },
{ x: 0.87, y: 0.55, label: 'RH3' },
{ x: 0.84, y: 0.60, label: 'RH4' },
// Chest/spine
{ x: 0.50, y: 0.35, label: 'CHEST' },
{ x: 0.50, y: 0.45, label: 'MID' },
{ x: 0.50, y: 0.55, label: 'LOW' },
{ x: 0.50, y: 0.65, label: 'HIP' },
].map(p => ({ ...p, x: p.x * width, y: p.y * height }));
}
function draw() {
background(12, 12, 24);
// Body silhouette
drawBodySilhouette();
// Electrode circles
for (let i = 0; i < 12; i++) {
let ep = electrodePositions[i];
let col = touchStates[i] ? color(80, 200, 255) : color(30, 50, 80);
fill(col);
noStroke();
ellipse(ep.x, ep.y, touchStates[i] ? 28 : 18);
if (touchStates[i]) {
noFill();
stroke(80, 200, 255, 80);
strokeWeight(1);
ellipse(ep.x, ep.y, 50 + sin(frameCount * 0.1) * 10);
}
}
// Breathing waveform (electrode 8 = chest)
drawBreathWaveform();
}
function drawBodySilhouette() {
noFill();
stroke(40, 40, 80);
strokeWeight(1.5);
// Head
ellipse(width / 2, height * 0.12, 80, 90);
// Torso
beginShape();
vertex(width * 0.35, height * 0.20);
vertex(width * 0.30, height * 0.22);
vertex(width * 0.15, height * 0.40);
vertex(width * 0.18, height * 0.70);
vertex(width * 0.38, height * 0.72);
vertex(width * 0.62, height * 0.72);
vertex(width * 0.82, height * 0.70);
vertex(width * 0.85, height * 0.40);
vertex(width * 0.70, height * 0.22);
vertex(width * 0.65, height * 0.20);
endShape(CLOSE);
}
function drawBreathWaveform() {
if (breathData.length < 2) return;
noFill();
stroke(100, 255, 180, 150);
strokeWeight(1.5);
beginShape();
for (let i = 0; i < breathData.length; i++) {
let x = map(i, 0, MAX_BREATH - 1, 20, width - 20);
let y = map(breathData[i], -50, 50, height - 40, height - 120);
vertex(x, y);
}
endShape();
}
// parseLine sets touchStates and pushes breathing data
function parseLine(line) {
line = line.trim();
if (line.startsWith('T:')) {
let n = parseInt(line.slice(2));
if (!isNaN(n) && n < 12) touchStates[n] = true;
} else if (line.startsWith('R:')) {
let n = parseInt(line.slice(2));
if (!isNaN(n) && n < 12) touchStates[n] = false;
} else if (line.startsWith('BR:')) {
let delta = parseFloat(line.slice(3));
if (!isNaN(delta)) {
breathData.push(delta);
if (breathData.length > MAX_BREATH) breathData.shift();
}
}
}
Next Steps
- Add an accelerometer (MPU6050 via I²C) to detect posture and movement alongside touch
- Use the VS1053’s MIDI mode to generate sound without SD card tracks — free up the MCU to focus on wearable sensing
- Build a soft-robot interface where fabric electrodes control pneumatic actuators via a relay shield
- Explore
VELOSTAT(pressure-sensitive material) combined with conductive fabric electrodes for force sensing alongside capacitive touch