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
Video | I will insert video of the functioning circuit soon.
From Vimeo
Sound Waves from George Gally (Radarboy) on Vimeo.
From Youtube