Introduction: Training Tying Machine
Product Introduction:
The Training Tying Machine is designed to help elderly people strengthen their grip and knot-tying ability. Users pull the rope within a 60-second session, aiming for at least 30 pulls. For every 5 successful pulls, an additional LED lights up, providing clear feedback and motivation during training.
Supplies
Tension Sensor
LED
Big Button
Subscribe to 深夜溫牛奶
Step 1: Make Tension Sensor
first of all, we need to tie 2 wire on the rope. and add some coal on the rope.
and we connect the wires to the breadboard, and we repeat 5 times.(make 5)
Step 2: Button
The button is makes cause we need to reset the machine.
button have 2 wires and connect them to the breadboard.
Step 3: The Program
// === 參數 ===
const int SENSOR_PIN = A0;
const unsigned long GAME_MS = 60000; // 遊戲 60 秒
// 平滑/防抖
const int SMOOTH_N = 8; // 平滑取樣數
const int STABLE_READS = 3; // 連續達標次數才承認
const unsigned long REFRACT_MS = 150; // 計分後的不應期(避免連跳)
// 門檻(開機會自動校正)
int TH_HIGH = 0; // 高門檻:超過才算「有拉」
int TH_LOW = 0; // 低門檻:跌破才算「放開」(形成回滯)
// 遊戲狀態
int score = 0;
bool gameActive = false;
unsigned long startTime = 0;
// 施密特狀態:0=低區(未拉), 1=高區(拉著)
int schmittState = 0;
int stableCounterHigh = 0, stableCounterLow = 0;
unsigned long refractUntil = 0;
// 玩家/榜單
const char* playerName = "Danny";
int bestScore = 0;
const char* bestName = "Danny";
// LED
const int leds[5] = {5, 6, 7, 8, 9};
// 按鈕
const int resetBtn = 2;
int lastBtnState = HIGH;
// 瞬間變化法
int lastVal = 0;
const int JUMP_TH = 100; // 瞬間跳動門檻
void setup() {
Serial.begin(9600);
while (!Serial) {;}
for (int i = 0; i < 5; i++) {
pinMode(leds[i], OUTPUT);
digitalWrite(leds[i], LOW);
}
pinMode(resetBtn, INPUT_PULLUP);
// ── 自動校正:偵測靜止時的基線與雜訊 ──
calibrateThresholds();
startGame();
}
void loop() {
// 邊緣觸發重置
int btnState = digitalRead(resetBtn);
if (lastBtnState == HIGH && btnState == LOW) resetGame();
lastBtnState = btnState;
if (!gameActive) return;
// 讀感測 + 平滑
int val = readSmoothed();
// --- 模式 1:瞬間變化偵測 ---
int delta = abs(val - lastVal);
if (delta >= JUMP_TH && millis() >= refractUntil) {
addScore("⚡ 跳動觸發");
}
lastVal = val;
// --- 模式 2:施密特觸發 + 連讀防抖 ---
if (val >= TH_HIGH) {
stableCounterHigh++;
stableCounterLow = 0;
if (schmittState == 0 && stableCounterHigh >= STABLE_READS) {
schmittState = 1; // 進入高區(拉住)
}
} else if (val <= TH_LOW) {
stableCounterLow++;
stableCounterHigh = 0;
if (schmittState == 1 && stableCounterLow >= STABLE_READS) {
if (millis() >= refractUntil) {
addScore("✅ 高低觸發");
}
schmittState = 0; // 回到低區
}
} else {
stableCounterHigh = 0;
stableCounterLow = 0;
}
// 顯示監控
Serial.print("A0=");
Serial.print(val);
Serial.print(" | Δ=");
Serial.print(delta);
Serial.print(" | 分數=");
Serial.print(score);
Serial.print(" | TH_LOW=");
Serial.print(TH_LOW);
Serial.print(" TH_HIGH=");
Serial.println(TH_HIGH);
// 時間到
if (millis() - startTime >= GAME_MS) {
gameActive = false;
Serial.println("⏰ 時間到!");
Serial.print("你的分數: ");
Serial.println(score);
if (score > bestScore) { bestScore = score; bestName = playerName; }
if (score >= 30) Serial.println("🎉 恭喜你完成挑戰!");
else Serial.println("😢 沒達到 30 次,下次加油!");
Serial.print("🏆 第一名: ");
Serial.print(bestName);
Serial.print(" - ");
Serial.print(bestScore);
Serial.println(" 分");
// 全亮閃爍
for (int k = 0; k < 5; k++) {
for (int i = 0; i < 5; i++) digitalWrite(leds[i], HIGH);
delay(300);
for (int i = 0; i < 5; i++) digitalWrite(leds[i], LOW);
delay(300);
}
}
delay(20); // 小延遲
}
void startGame() {
Serial.println("🎮 遊戲由 Danny 製作 🎮");
Serial.println("準備開始...");
Serial.println("2...");
delay(800);
Serial.println("1...");
delay(800);
Serial.println("👉 開始!快拉!");
score = 0;
gameActive = true;
startTime = millis();
schmittState = 0;
stableCounterHigh = stableCounterLow = 0;
refractUntil = 0;
lastVal = analogRead(SENSOR_PIN);
for (int i = 0; i < 5; i++) digitalWrite(leds[i], LOW);
}
void resetGame() {
Serial.println("🔄 遊戲重置!");
calibrateThresholds(); // 重新校正
startGame();
}
// 統一加分處理
void addScore(const char* reason) {
score++;
refractUntil = millis() + REFRACT_MS;
Serial.print(reason);
Serial.print("!分數 = ");
Serial.println(score);
lightLedsByScore();
}
// 依分數點亮 LED(每 5 分一顆)
void lightLedsByScore() {
int lit = min(score / 5, 5);
for (int i = 0; i < 5; i++) digitalWrite(leds[i], (i < lit) ? HIGH : LOW);
}
// 平滑讀取:多次取樣,去頭去尾,再平均
int readSmoothed() {
int minv = 1023, maxv = 0;
long sum = 0;
for (int i = 0; i < SMOOTH_N; i++) {
int v = analogRead(SENSOR_PIN);
sum += v;
if (v < minv) minv = v;
if (v > maxv) maxv = v;
delayMicroseconds(200);
}
if (SMOOTH_N >= 5) {
sum -= minv;
sum -= maxv;
return sum / (SMOOTH_N - 2);
} else {
return sum / SMOOTH_N;
}
}
// 開機/重置時自動校正門檻
void calibrateThresholds() {
const int CAL_N = 200;
int minv = 1023, maxv = 0;
long sum = 0;
Serial.println("🛠 校正中,請先不要拉...");
for (int i = 0; i < CAL_N; i++) {
int v = analogRead(SENSOR_PIN);
sum += v;
if (v < minv) minv = v;
if (v > maxv) maxv = v;
delay(3);
}
int baseline = sum / CAL_N;
int noise = max(5, maxv - minv);
TH_HIGH = baseline + max(40, noise * 3);
TH_LOW = baseline + max(20, noise * 1);
if (TH_LOW >= TH_HIGH) TH_LOW = TH_HIGH - 5;
TH_HIGH = constrain(TH_HIGH, 0, 1023);
TH_LOW = constrain(TH_LOW, 0, TH_HIGH - 1);
Serial.print("✅ 校正完成:baseline=");
Serial.print(baseline);
Serial.print(" TH_LOW=");
Serial.print(TH_LOW);
Serial.print(" TH_HIGH=");
Serial.println(TH_HIGH);
}



