Training Tying Machine

by DannyEggyParty in Circuits > Arduino

13 Views, 0 Favorites, 0 Comments

Training Tying Machine

IMG_2096.jpeg
IMG_2094.jpeg

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 深夜溫牛奶

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)

Button

The button is makes cause we need to reset the machine.

button have 2 wires and connect them to the breadboard.

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

}