Skip to content

9. Input devices

What I Learned — Capacitive Touch and the ATtiny PTC

Working with this board taught me two things in parallel: how to read a sensor you can’t directly see, and what is actually happening inside the microcontroller when it detects touch.

The science of capacitive sensing:

The ATtiny1614 has a built-in peripheral called the PTC — Peripheral Touch Controller. This is dedicated hardware inside the chip specifically designed to measure capacitance. Here is what it actually does:

The PTC charges the copper pad up to a known voltage, then stops and lets it discharge. It measures precisely how long that discharge takes. This timing is everything: * A bare pad with no finger present has a small, fixed capacitance — it discharges quickly

  • When a finger approaches, the finger and the pad together form a capacitor. Your skin is conductive, and the air or PCB surface between your finger and the pad acts as the insulator. This adds capacitance to the system — the pad now holds more charge and takes longer to discharge

  • The PTC detects that longer discharge time and registers it as a touch

So what the pin is really measuring is time — specifically, the time it takes for voltage to fall from its charged state back down to zero. A longer fall time means more capacitance. More capacitance means a finger is present. It is an elegant use of physics — no moving parts, no external sensor, just a copper pad and a timer.

This also explains why my readings were decreasing when touched rather than increasing — the raw value returned by analogRead on a PTC pin reflects the discharge speed inversely. A slower discharge returns a lower number in this implementation, which is why touch was detected when the reading dropped below the threshold rather than above it.

Board Design and Fabrication — Reference

The board used for this assignment was designed in Week 6 (Electronics Design) and produced in Week 8 (Electronics Production). Rather than repeat the full process here, the key points relevant to the touch input are:

  • The pads: The three capacitive touch pads are not separate components — they are drawn directly onto the copper layer of the PCB in KiCad. Each pad is a filled circle drawn using the Circle command, filled solid via the properties panel, and labeled PAD 1, PAD 2, PAD 3. Their size was chosen to give a large enough sensing area for a fingertip while keeping them physically separated on the board.

  • Clearances: Each pad connects to its ATtiny pin through a small series resistor for ESD protection. Clearances between the pad copper and surrounding traces were kept within KiCad’s design rule limits. In practice, the traces were still close enough to cause occasional crosstalk — something to improve in future revisions by increasing the spacing around the pad areas.

  • Pin assignment: The three pads connect to PA4, PA5, and PA6 — the ATtiny1614’s dedicated PTC peripheral pins. These pins have the hardware sensing capability built in. No external circuitry is needed beyond the series resistors.

[SEE Week 6 & 8]

Noise and Sensitivity

Capacitive touch sounds simple in theory but in practice it is sensitive to everything around it. This was probably the most useful lesson of the week.

The core problem is that the baseline reading is not fixed — it shifts with the electrical environment of the room. If someone walks past, if a phone is nearby, if the power supply fluctuates slightly, the baseline drifts. When it drifts far enough it crosses the threshold and triggers a false touch. At one point the entire board was reading as touched, almost certainly because the traces running between the pads were close enough to couple capacitance between them — the board was essentially sensing itself.

A few things helped bring it under control: * Setting the threshold comfortably above the noise floor but clearly below the touch response — the blink debugging table was essential for finding this range

  • Keeping other conductive objects away from the board during testing

  • Accepting that some sensitivity to environment is inherent at this scale

  • The deeper fix — wider spacing between pad traces, a more deliberate ground strategy, and potentially a hardware filter capacitor — is something to carry into the next board revision.

How the Code Works

The code logic is straightforward and worth walking through clearly because it translates directly from the physics described above.

Defining pins and threshold:

const int LED_PIN = PIN_PA1;
const int TOUCH_PIN = PIN_PA4;
const int THRESHOLD = 100;
bool isTouched = false;
  • LED_PIN and TOUCH_PIN are just readable nicknames for the physical pin numbers. THRESHOLD is the decision boundary — the number the raw reading has to drop below for a touch to be registered. isTouched is a simple true/false flag that carries the touch state through the loop.

Setup:

cppvoid setup() {
  pinMode(LED_PIN, OUTPUT);
}

The LED pin is set as an output — it will send voltage out to drive the LED. The touch pin needs no setup here because analogRead handles it automatically.

The loop:

int reading = analogRead(TOUCH_PIN);
if (reading < THRESHOLD) {
  isTouched = true;
} else {
  isTouched = false;
}

Every cycle, analogRead samples the discharge time on the touch pin and returns a number. If that number is below the threshold the flag flips to true. If above, it stays false.

The response:

cppif (isTouched) {
  digitalWrite(LED_PIN, HIGH);
  delay(200);
  digitalWrite(LED_PIN, LOW);
  delay(1800);
} else {
  digitalWrite(LED_PIN, LOW);
}

If touched — LED on for 200ms, off for 1800ms, repeat. If not touched — LED stays off. Simple, readable, and easy to modify. Scaling to three pads: The three-pad version follows the same logic but reads three pins sequentially and assigns a different blink delay to each. Pad 1 blinks slowly (1800ms gap), Pad 2 at medium speed (800ms gap), Pad 3 rapidly (300ms gap). The else if chain means only one pad triggers at a time — the first one detected below threshold wins that cycle.

Single Pad Test

const int LED_PIN = PIN_PA1;
const int TOUCH_PIN = PIN_PA4;
const int THRESHOLD = 100;
bool isTouched = false;

void setup() {
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  int reading = analogRead(TOUCH_PIN);

  if (reading < THRESHOLD) {
    isTouched = true;
  } else {
    isTouched = false;
  }

  if (isTouched) {
    digitalWrite(LED_PIN, HIGH);
    delay(200);
    digitalWrite(LED_PIN, LOW);
    delay(1800);
  } else {
    digitalWrite(LED_PIN, LOW);
  }
}

All Three Pads

const int LED_PIN = PIN_PA1;
const int TOUCH_1_PIN = PIN_PA4;
const int TOUCH_2_PIN = PIN_PA5;
const int TOUCH_3_PIN = PIN_PA6;
const int THRESHOLD = 30;

void setup() {
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  int reading1 = analogRead(TOUCH_1_PIN);
  int reading2 = analogRead(TOUCH_2_PIN);
  int reading3 = analogRead(TOUCH_3_PIN);

  if (reading1 < THRESHOLD) {
    // Pad 1 — slow blink
    digitalWrite(LED_PIN, HIGH);
    delay(200);
    digitalWrite(LED_PIN, LOW);
    delay(1800);
  } else if (reading2 < THRESHOLD) {
    // Pad 2 — medium blink
    digitalWrite(LED_PIN, HIGH);
    delay(200);
    digitalWrite(LED_PIN, LOW);
    delay(800);
  } else if (reading3 < THRESHOLD) {
    // Pad 3 — fast blink
    digitalWrite(LED_PIN, HIGH);
    delay(200);
    digitalWrite(LED_PIN, LOW);
    delay(300);
  } else {
    digitalWrite(LED_PIN, LOW);
  }
}

Video