Wearables and E-textiles

Integrate the Touch Board into garments and body-worn interfaces using conductive fabric, thread, and LiPo power.

⏱ 65 min wearables e-textiles conductive fabric LiPo body interface

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:

  1. Enclose the Touch Board and battery in a waterproof zip pouch (e.g. silicone phone pouch) or remove them before washing
  2. Seal all conductive thread connections with fabric-compatible insulating varnish
  3. Test the electrical connections after every third wash — resistance tends to increase as the metallic coating on conductive thread oxidises
  4. 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