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);

}