4. Embedded Programming

This week I worked on the sensors for my final project. I worked with Grecia Bello to understand the process of capacitive sensors and choosing the correct boards for the process described in my final project. I am hoping to create a touch sensor reaction that would trigger the motors and gears of my interactive mascot.

Research

I am currently completing Fabricademy and this is also my final project for Fabricademy During the final project creation period of fabricademy, my mentors advised me to be aware that my original concept might be unstable. Initially, I wanted to create a touch sensor utikzing faux fur and conductive threads woven into the faux fur. For many reasons, this style of sensor would take multiple iterations to create a consistent reaction (signal to the motors to activate). I was advised to explore other methods like pressure sensors that could still allow the audience a "stroking" motion in their interaction. At that point I reached out to Grecia for support, as she is very talented and had offered to help during her Chevron fellowship visit to the FVSU Fab Lab in Fall 2025.

Code Example | Touch Sensor

    /*
    ESP32 + 2 Servos + Capacitive Touch (GPIO4 / T0)
    - Touch: GPIO4 (T0) connected to aluminum foil
    - Servos: Left=GPIO18, Right=GPIO19
    Notes:
    - Power servos from external 5V (step-up) NOT from ESP32 3.3V.
    - All grounds must be common (ESP32 GND tied to servo GND).
  */
  
  #include 
  #include 
  
  // ------------------ Pins ------------------
  static const int PIN_TOUCH   = 4;   // Touch T0 (GPIO4)
  static const int PIN_SERVO_L = 18;  // Left jaw servo signal
  static const int PIN_SERVO_R = 19;  // Right jaw servo signal
  
  // ------------------ Touch calibration ------------------
  int touchBaseline  = 0;
  int touchThreshold = 0;   // touched when touchRead < threshold
  
  // Gesture timings (ms)
  static const uint32_t TAP_MAX_MS        = 220;
  static const uint32_t DOUBLE_TAP_GAP_MS = 350;
  static const uint32_t LONG_PRESS_MS     = 650;
  
  // ------------------ Servo settings ------------------
  // Adjust angles to your jaw mechanics
  int L_CLOSED = 75;
  int L_OPEN   = 110;
  
  int R_CLOSED = 105;  // mirrored side (often reversed)
  int R_OPEN   = 70;
  
  Servo servoL;
  Servo servoR;
  
  // ------------------ Touch gesture state ------------------
  bool isTouched = false;
  uint32_t touchStartMs = 0;
  uint32_t lastReleaseMs = 0;
  int tapCount = 0;
  
  // ------------------ Helpers ------------------
  void setJaw(int lAngle, int rAngle) {
    servoL.write(lAngle);
    servoR.write(rAngle);
  }
  
  void jawSmoothMove(int lFrom, int rFrom, int lTo, int rTo, int steps = 25, int stepDelayMs = 10) {
    for (int i = 0; i <= steps; i++) {
      float t = (float)i / (float)steps;
      int l = (int)(lFrom + (lTo - lFrom) * t);
      int r = (int)(rFrom + (rTo - rFrom) * t);
      setJaw(l, r);
      delay(stepDelayMs);
    }
  }
  
  void jawChomp(int cycles, int openHoldMs = 80, int closeHoldMs = 60) {
    for (int i = 0; i < cycles; i++) {
      jawSmoothMove(L_CLOSED, R_CLOSED, L_OPEN, R_OPEN, 18, 8);
      delay(openHoldMs);
      jawSmoothMove(L_OPEN, R_OPEN, L_CLOSED, R_CLOSED, 18, 8);
      delay(closeHoldMs);
    }
  }
  
  // Actions
  void doSingleTap() {
    // 1 gentle chomp
    jawChomp(1, 100, 90);
  }
  
  void doDoubleTap() {
    // 3 quick chomps
    jawChomp(3, 60, 50);
  }
  
  void doLongPress() {
    // open and hold, then slow close
    jawSmoothMove(L_CLOSED, R_CLOSED, L_OPEN, R_OPEN, 22, 10);
    delay(450);
    jawSmoothMove(L_OPEN, R_OPEN, L_CLOSED, R_CLOSED, 35, 12);
  }
  
  void calibrateTouch() {
    long sum = 0;
    const int N = 30;
    for (int i = 0; i < N; i++) {
      sum += touchRead(PIN_TOUCH);
      delay(15);
    }
    touchBaseline = (int)(sum / N);
  
    // margin: increase if it triggers too easily; decrease if not sensitive enough
    int margin = 20;
    touchThreshold = touchBaseline - margin;
  
    Serial.print("Touch baseline=");
    Serial.print(touchBaseline);
    Serial.print("  threshold=");
    Serial.println(touchThreshold);
  }
  
  bool touchDetected() {
    int v = touchRead(PIN_TOUCH);
    return (v < touchThreshold);
  }
  
  void setupServos() {
    servoL.setPeriodHertz(50);
    servoR.setPeriodHertz(50);
  
    // Pulse range helps compatibility with many micro servos
    servoL.attach(PIN_SERVO_L, 500, 2400);
    servoR.attach(PIN_SERVO_R, 500, 2400);
  
    setJaw(L_CLOSED, R_CLOSED);
  }
  
  void setup() {
    Serial.begin(115200);
    delay(250);
  
    setupServos();
    calibrateTouch();
  
    Serial.println("Ready (Touch + Servos).");
  }
  
  void loop() {
    bool nowTouched = touchDetected();
    uint32_t now = millis();
  
    // Touch started
    if (!isTouched && nowTouched) {
      isTouched = true;
      touchStartMs = now;
    }
  
    // Touch ended
    if (isTouched && !nowTouched) {
      isTouched = false;
      uint32_t heldMs = now - touchStartMs;
      lastReleaseMs = now;
  
      if (heldMs >= LONG_PRESS_MS) {
        tapCount = 0;
        doLongPress();
      } else if (heldMs <= TAP_MAX_MS) {
        tapCount++;
      } else {
        tapCount = 0;
        doSingleTap();
      }
    }
  
    // Decide single vs double tap after gap
    if (tapCount > 0 && (now - lastReleaseMs) > DOUBLE_TAP_GAP_MS) {
      if (tapCount == 1) doSingleTap();
      else doDoubleTap();
      tapCount = 0;
    }
  
    delay(10);
  }
  

Gallery

Electronic Schematics Interactive Wildcat

Video | I will insert video of the functioning circuit soon.

From Vimeo

Sound Waves from George Gally (Radarboy) on Vimeo.


From Youtube