From f59d63ae83ee8fce767041487ec61fc4164eafdf Mon Sep 17 00:00:00 2001 From: seo Date: Sun, 7 Jun 2026 01:08:04 +0900 Subject: [PATCH] Initial project import --- Auto_Seat.ino | 665 ++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 25 ++ report.html | 305 +++++++++++++++++++++++ upload.ps1 | 13 + 4 files changed, 1008 insertions(+) create mode 100644 Auto_Seat.ino create mode 100644 README.md create mode 100644 report.html create mode 100644 upload.ps1 diff --git a/Auto_Seat.ino b/Auto_Seat.ino new file mode 100644 index 0000000..4132657 --- /dev/null +++ b/Auto_Seat.ino @@ -0,0 +1,665 @@ +// +// Auto_Seat +// Automatic seat controller with EEPROM settings and optional serial tools. +// +#include + +#define seatRelayD8 8 // HIGH = OFF, LOW = ON (up relay) +#define seatRelayD9 9 // HIGH = OFF, LOW = ON (down relay) + +#define parkingPin A0 // PARKING raw analog value, 0~1023 +#define ignitionPin 7 // HIGH = IG1 ON, LOW = IG1 OFF + +// ============================================================ +// !!! SAFETY LIMIT !!! +// +// Physical maximum travel time from bottom to the highest allowed point. +// User settings saved in EEPROM must stay inside 0..6000ms. +// ============================================================ +const uint16_t SEAT_HARD_LIMIT_UP_MS = 6000; + +// Parking raw threshold hysteresis. +// P turns ON at 950 or higher, and turns OFF at 940 or lower. +const int PARKING_ON_THRESHOLD = 950; +const int PARKING_OFF_THRESHOLD = 940; + +// Down movement is estimated as 1.1x faster than up movement. +const int DOWN_SPEED_NUM = 11; +const int DOWN_SPEED_DEN = 10; + +// Input and relay protection. +const unsigned long IGNITION_STABILIZE_MS = 1000UL; +const unsigned long DRIVE_CONFIRM_MS = 80UL; +const unsigned long PARK_CONFIRM_MS = 250UL; +const unsigned long RELAY_DEADTIME_MS = 120UL; +const unsigned long DOWN_EDGE_MARGIN_MS = 300UL; + +// After 10 ignition OFF events, wait 5 minutes while ignition is OFF, +// then auto-calibrate: up 7 seconds -> down 6 seconds -> set position to bottom. +const byte USES_BEFORE_AUTO_CALIBRATION = 10; +const unsigned long AUTO_CALIBRATION_DELAY_MS = 300000UL; +const unsigned long AUTO_CALIBRATION_UP_MS = 7000UL; +const unsigned long AUTO_CALIBRATION_DOWN_MS = 6000UL; + +// Serial debug output. +const unsigned long DEBUG_INTERVAL_MS = 1000UL; +const long MANUAL_STEP_MS = 500; + +// EEPROM map. +const int EEPROM_MAGIC_ADDR = 0; +const byte EEPROM_MAGIC_VALUE = 0x64; +const int EEPROM_USE_COUNT_ADDR = 1; +const int EEPROM_SEAT_POSITION_ADDR = 2; // uint16_t, low byte first + +const byte CALIBRATION_IDLE = 0; +const byte CALIBRATION_UP = 1; +const byte CALIBRATION_DOWN = 2; + +boolean ignitionState = LOW; +boolean parkingState = LOW; +boolean prevIgnitionState = LOW; +boolean isStabilizing = false; +boolean confirmedDriveMode = LOW; +boolean pendingDriveMode = LOW; + +byte useCount = 0; +byte autoCalibrationState = CALIBRATION_IDLE; +bool autoCalibrationPending = false; +bool forceCalibration = false; +bool manualMode = false; + +int parkingRawValue = 0; +uint16_t storedSeatPositionMs = 0; +long currentPosMs = 0; +long targetPosMs = 0; +long downSpeedRemainder = 0; +unsigned long downEdgeMarginTime = DOWN_EDGE_MARGIN_MS; +bool downEdgeMarginActive = false; + +unsigned long lastTime = 0; +unsigned long stabilizeStartTime = 0; +unsigned long ignitionOffStartTime = 0; +unsigned long calibrationPhaseStartTime = 0; +unsigned long pendingDriveModeStartTime = 0; +unsigned long relayDeadtimeStartTime = 0; +unsigned long lastDebugPrintTime = 0; + +int currentSeatAction = 0; +int requestedSeatAction = 0; +int lastStoppedSeatAction = 0; + +bool serialIntroPrinted = false; +bool debugEnabled = false; +bool manualStepStatusPending = false; + +void setup() { + Serial.begin(115200); + Serial.setTimeout(25); + + pinMode(parkingPin, INPUT); + pinMode(ignitionPin, INPUT); + pinMode(seatRelayD8, OUTPUT); + pinMode(seatRelayD9, OUTPUT); + + applySeatAction(0); + seat(0); + loadSettings(); + + measurement(); + confirmedDriveMode = getRawDriveMode(); + pendingDriveMode = confirmedDriveMode; + targetPosMs = getTargetForConfirmedMode(); + if (ignitionState == LOW) { + ignitionOffStartTime = millis(); + } + + lastTime = millis(); +} + +void loop() { + measurement(); + handleSerial(); + + unsigned long currentTime = millis(); + unsigned long deltaTime = currentTime - lastTime; + lastTime = currentTime; + + if (prevIgnitionState == LOW && ignitionState == HIGH) { + isStabilizing = true; + stabilizeStartTime = currentTime; + } else if (prevIgnitionState == HIGH && ignitionState == LOW) { + registerIgnitionOffUse(currentTime); + } + prevIgnitionState = ignitionState; + + if (autoCalibrationState != CALIBRATION_IDLE) { + updateAutoCalibration(currentTime); + updateRelayOutput(currentTime); + updateDebugOutput(currentTime); + return; + } + + if (autoCalibrationPending && ignitionState == LOW && + currentTime - ignitionOffStartTime >= AUTO_CALIBRATION_DELAY_MS) { + startAutoCalibration(currentTime); + updateRelayOutput(currentTime); + updateDebugOutput(currentTime); + return; + } + + if (isStabilizing) { + if (currentTime - stabilizeStartTime < IGNITION_STABILIZE_MS) { + seat(0); + updateRelayOutput(currentTime); + updateDebugOutput(currentTime); + return; + } + isStabilizing = false; + } + + updateConfirmedDriveMode(currentTime); + if (!manualMode) { + targetPosMs = getTargetForConfirmedMode(); + } + normalizeMotionState(); + updateMovement(deltaTime); + normalizeMotionState(); + updateRelayOutput(currentTime); + updateManualStepStatus(); + updateDebugOutput(currentTime); +} + +void measurement() { + ignitionState = digitalRead(ignitionPin); + + parkingRawValue = analogRead(parkingPin); + if (parkingState == LOW && parkingRawValue >= PARKING_ON_THRESHOLD) { + parkingState = HIGH; + } else if (parkingState == HIGH && parkingRawValue <= PARKING_OFF_THRESHOLD) { + parkingState = LOW; + } +} + +boolean getRawDriveMode() { + if (ignitionState == HIGH && parkingState == LOW) { + return HIGH; + } + return LOW; +} + +long getTargetForConfirmedMode() { + if (confirmedDriveMode == HIGH) { + return storedSeatPositionMs; + } + return 0; +} + +void updateConfirmedDriveMode(unsigned long currentTime) { + boolean rawDriveMode = getRawDriveMode(); + + if (rawDriveMode != pendingDriveMode) { + pendingDriveMode = rawDriveMode; + pendingDriveModeStartTime = currentTime; + } + + unsigned long confirmMs = pendingDriveMode == HIGH ? DRIVE_CONFIRM_MS : PARK_CONFIRM_MS; + if (pendingDriveMode != confirmedDriveMode && + currentTime - pendingDriveModeStartTime >= confirmMs) { + confirmedDriveMode = pendingDriveMode; + manualMode = false; + manualStepStatusPending = false; + } +} + +void updateMovement(unsigned long deltaTime) { + if (downEdgeMarginActive) { + if (currentPosMs == 0 && targetPosMs == 0) { + seat(-1); + downEdgeMarginTime += deltaTime; + if (downEdgeMarginTime >= DOWN_EDGE_MARGIN_MS) { + downEdgeMarginTime = DOWN_EDGE_MARGIN_MS; + downEdgeMarginActive = false; + seat(0); + } + return; + } + + downEdgeMarginActive = false; + downEdgeMarginTime = DOWN_EDGE_MARGIN_MS; + } + + if (currentPosMs < targetPosMs) { + seat(1); + currentPosMs += deltaTime; + if (currentPosMs >= targetPosMs) { + currentPosMs = targetPosMs; + seat(0); + } + } else if (currentPosMs > targetPosMs) { + seat(-1); + long downMoveScaled = deltaTime * DOWN_SPEED_NUM + downSpeedRemainder; + currentPosMs -= downMoveScaled / DOWN_SPEED_DEN; + downSpeedRemainder = downMoveScaled % DOWN_SPEED_DEN; + if (currentPosMs <= targetPosMs) { + currentPosMs = targetPosMs; + downSpeedRemainder = 0; + if (targetPosMs == 0) { + startDownEdgeMargin(); + } else { + seat(0); + } + } + } else { + seat(0); + } +} + +void startDownEdgeMargin() { + downEdgeMarginActive = true; + downEdgeMarginTime = 0; + seat(-1); +} + +void registerIgnitionOffUse(unsigned long currentTime) { + ignitionOffStartTime = currentTime; + + if (useCount < USES_BEFORE_AUTO_CALIBRATION) { + useCount++; + EEPROM.update(EEPROM_USE_COUNT_ADDR, useCount); + } + + if (useCount >= USES_BEFORE_AUTO_CALIBRATION) { + autoCalibrationPending = true; + } +} + +void startAutoCalibration(unsigned long currentTime) { + manualMode = false; + manualStepStatusPending = false; + downSpeedRemainder = 0; + downEdgeMarginActive = false; + downEdgeMarginTime = DOWN_EDGE_MARGIN_MS; + autoCalibrationState = CALIBRATION_UP; + calibrationPhaseStartTime = currentTime; + seat(1); +} + +void updateAutoCalibration(unsigned long currentTime) { + if (ignitionState == HIGH && !forceCalibration) { + seat(0); + autoCalibrationState = CALIBRATION_IDLE; + manualStepStatusPending = false; + downSpeedRemainder = 0; + downEdgeMarginActive = false; + downEdgeMarginTime = DOWN_EDGE_MARGIN_MS; + ignitionOffStartTime = currentTime; + return; + } + + if (autoCalibrationState == CALIBRATION_UP) { + if (currentTime - calibrationPhaseStartTime >= AUTO_CALIBRATION_UP_MS) { + autoCalibrationState = CALIBRATION_DOWN; + calibrationPhaseStartTime = currentTime; + seat(-1); + } else { + seat(1); + } + } else if (autoCalibrationState == CALIBRATION_DOWN) { + if (currentTime - calibrationPhaseStartTime >= AUTO_CALIBRATION_DOWN_MS) { + seat(0); + autoCalibrationState = CALIBRATION_IDLE; + autoCalibrationPending = false; + forceCalibration = false; + useCount = 0; + EEPROM.update(EEPROM_USE_COUNT_ADDR, useCount); + currentPosMs = 0; + targetPosMs = 0; + downSpeedRemainder = 0; + downEdgeMarginActive = false; + downEdgeMarginTime = DOWN_EDGE_MARGIN_MS; + } else { + seat(-1); + } + } +} + +void handleSerial() { + if (!Serial.available()) { + return; + } + + String command = Serial.readStringUntil('\n'); + command.trim(); + + if (debugEnabled) { + command.toLowerCase(); + if (command == "x") { + debugEnabled = false; + Serial.println(F("DEBUG_STOPPED")); + printStatus(F("STATUS")); + } + return; + } + + if (!serialIntroPrinted) { + if (command.length() == 0) { + printIntro(); + serialIntroPrinted = true; + } + return; + } + + if (command.length() == 0) { + return; + } + + processSerialCommand(command); +} + +bool isSeatCommandBusy() { + return manualStepStatusPending || + autoCalibrationState != CALIBRATION_IDLE || + downEdgeMarginActive || + currentPosMs != targetPosMs || + requestedSeatAction != 0 || + currentSeatAction != 0; +} + +void processSerialCommand(String command) { + command.trim(); + command.toLowerCase(); + if (command.length() == 0) { + printStatus(F("EMPTY_INPUT_STATUS")); + return; + } + + if (command.length() != 1) { + Serial.println(F("ERR: one-character command only. Send h for help.")); + printStatus(F("ACK")); + return; + } + + char name = command.charAt(0); + + if (requiresParkingForCommand(name) && parkingState != HIGH) { + Serial.println(F("ERR: check gear state. Command requires P.")); + printStatus(F("GEAR_CHECK_FAILED")); + return; + } + + if (isSeatCommandBusy()) { + Serial.println(F("ERR: busy. Wait until seat movement is done.")); + printStatus(F("BUSY")); + return; + } + + if (name == 'h') { + printIntro(); + } else if (name == 's') { + printStatus(F("STATUS")); + } else if (name == 'p') { + saveCurrentPositionCommand(); + } else if (name == 'u') { + handleRelativeStep(1); + } else if (name == 'd') { + handleRelativeStep(-1); + } else if (name == 'g') { + debugEnabled = true; + lastDebugPrintTime = 0; + printStatus(F("DEBUG_STARTED_X_TO_STOP")); + } else if (name == 'a') { + manualStepStatusPending = false; + manualMode = false; + targetPosMs = getTargetForConfirmedMode(); + printStatus(F("AUTO_MODE_RESTORED")); + } else if (name == 'c') { + manualStepStatusPending = false; + forceCalibration = true; + autoCalibrationPending = false; + startAutoCalibration(millis()); + printStatus(F("CALIBRATION_STARTED_UP7_DOWN6")); + } else if (name == 'r') { + useCount = 0; + autoCalibrationPending = false; + EEPROM.update(EEPROM_USE_COUNT_ADDR, useCount); + printStatus(F("USE_COUNT_RESET")); + } else { + Serial.println(F("Unknown command. Send h for help.")); + printStatus(F("ACK")); + } +} + +bool requiresParkingForCommand(char name) { + return name == 'p' || + name == 'u' || + name == 'd' || + name == 'a' || + name == 'c' || + name == 'r'; +} + +void saveCurrentPositionCommand() { + currentPosMs = clampPosition(currentPosMs); + targetPosMs = currentPosMs; + + storedSeatPositionMs = (uint16_t)currentPosMs; + writeUint16(EEPROM_SEAT_POSITION_ADDR, storedSeatPositionMs); + manualMode = true; + targetPosMs = currentPosMs; + printStatus(F("CURRENT_POSITION_SAVED")); +} + +void handleRelativeStep(int direction) { + long nextTarget = currentPosMs + (direction * MANUAL_STEP_MS); + if (nextTarget < 0 || nextTarget > SEAT_HARD_LIMIT_UP_MS) { + Serial.print(F("WARN: ignored. Requested target ")); + Serial.print(nextTarget); + Serial.print(F("ms is outside 0..")); + Serial.print(SEAT_HARD_LIMIT_UP_MS); + Serial.println(F("ms.")); + printStatus(F("ACK")); + return; + } + + manualMode = true; + targetPosMs = nextTarget; + manualStepStatusPending = true; +} + +void updateManualStepStatus() { + if (!manualStepStatusPending) { + return; + } + + if (currentPosMs == targetPosMs && requestedSeatAction == 0 && currentSeatAction == 0) { + manualStepStatusPending = false; + printStatus(F("MANUAL_STEP_DONE")); + } +} + +long clampPosition(long value) { + if (value < 0) { + return 0; + } + if (value > SEAT_HARD_LIMIT_UP_MS) { + return SEAT_HARD_LIMIT_UP_MS; + } + return value; +} + +void normalizeMotionState() { + currentPosMs = clampPosition(currentPosMs); + targetPosMs = clampPosition(targetPosMs); + + if (storedSeatPositionMs > SEAT_HARD_LIMIT_UP_MS) { + storedSeatPositionMs = SEAT_HARD_LIMIT_UP_MS; + } + + if (targetPosMs != 0 && downEdgeMarginActive) { + downEdgeMarginActive = false; + downEdgeMarginTime = DOWN_EDGE_MARGIN_MS; + } + + if (currentPosMs <= targetPosMs) { + downSpeedRemainder = 0; + } +} + +void updateDebugOutput(unsigned long currentTime) { + if (!debugEnabled) { + return; + } + + if (lastDebugPrintTime == 0 || currentTime - lastDebugPrintTime >= DEBUG_INTERVAL_MS) { + lastDebugPrintTime = currentTime; + printStatus(F("DEBUG")); + } +} + +void loadSettings() { + if (EEPROM.read(EEPROM_MAGIC_ADDR) != EEPROM_MAGIC_VALUE) { + EEPROM.update(EEPROM_MAGIC_ADDR, EEPROM_MAGIC_VALUE); + EEPROM.update(EEPROM_USE_COUNT_ADDR, 0); + writeUint16(EEPROM_SEAT_POSITION_ADDR, SEAT_HARD_LIMIT_UP_MS); + } + + useCount = EEPROM.read(EEPROM_USE_COUNT_ADDR); + if (useCount > USES_BEFORE_AUTO_CALIBRATION) { + useCount = 0; + EEPROM.update(EEPROM_USE_COUNT_ADDR, useCount); + } + autoCalibrationPending = (useCount >= USES_BEFORE_AUTO_CALIBRATION); + + storedSeatPositionMs = readUint16(EEPROM_SEAT_POSITION_ADDR); + if (storedSeatPositionMs > SEAT_HARD_LIMIT_UP_MS) { + storedSeatPositionMs = SEAT_HARD_LIMIT_UP_MS; + writeUint16(EEPROM_SEAT_POSITION_ADDR, storedSeatPositionMs); + } +} + +uint16_t readUint16(int addr) { + byte lowByte = EEPROM.read(addr); + byte highByte = EEPROM.read(addr + 1); + return (uint16_t)lowByte | ((uint16_t)highByte << 8); +} + +void writeUint16(int addr, uint16_t value) { + EEPROM.update(addr, value & 0xFF); + EEPROM.update(addr + 1, (value >> 8) & 0xFF); +} + +void printIntro() { + Serial.println(); + Serial.println(F("+--------------------------------------------------+")); + Serial.println(F("| Auto Seat Serial Console |")); + Serial.println(F("+--------------------------------------------------+")); + Serial.println(F("| Units: milliseconds. 0 = bottom. 6000 = top cap. |")); + Serial.println(F("| Normal auto move target is saved in EEPROM. |")); + Serial.println(F("| Action commands require gear P for safety. |")); + Serial.println(F("| Serial output is quiet unless a command is used. |")); + Serial.println(F("+--------------------------------------------------+")); + Serial.println(F("Commands:")); + Serial.println(F(" s Print current state once")); + Serial.println(F(" p Save current position to EEPROM")); + Serial.println(F(" u Manual up +500ms if inside 0..6000")); + Serial.println(F(" d Manual down -500ms if inside 0..6000")); + Serial.println(F(" g Debug every 1s; only x works in debug")); + Serial.println(F(" x Stop debug mode only")); + Serial.println(F(" d=down, g=debug to avoid command overlap")); + Serial.println(F(" a Return to gear-based automatic target")); + Serial.println(F(" c Run calibration now: up 7s, down 6s, set 0")); + Serial.println(F(" r Clear 10-use auto calibration counter")); + Serial.println(F(" h Print this help")); + Serial.println(F("+--------------------------------------------------+")); + printStatus(F("CURRENT_SETTINGS")); +} + +void printStatus(const __FlashStringHelper *tag) { + Serial.println(); + Serial.print(F("[")); + Serial.print(tag); + Serial.println(F("]")); + Serial.print(F("raw=")); + Serial.print(parkingRawValue); + Serial.print(F(" ig=")); + Serial.print(ignitionState == HIGH ? 1 : 0); + Serial.print(F(" p=")); + Serial.print(parkingState == HIGH ? 1 : 0); + Serial.print(F(" rawDrive=")); + Serial.print(getRawDriveMode() == HIGH ? 1 : 0); + Serial.print(F(" confirmedDrive=")); + Serial.println(confirmedDriveMode == HIGH ? 1 : 0); + + Serial.print(F("currentMs=")); + Serial.print(currentPosMs); + Serial.print(F(" targetMs=")); + Serial.print(targetPosMs); + Serial.print(F(" savedPositionMs=")); + Serial.print(storedSeatPositionMs); + Serial.print(F(" hardLimitMs=")); + Serial.println(SEAT_HARD_LIMIT_UP_MS); + + Serial.print(F("requestedRelay=")); + Serial.print(requestedSeatAction); + Serial.print(F(" actualRelay=")); + Serial.print(currentSeatAction); + Serial.print(F(" manualMode=")); + Serial.print(manualMode ? 1 : 0); + Serial.print(F(" stabilizing=")); + Serial.println(isStabilizing ? 1 : 0); + + Serial.print(F("useCount=")); + Serial.print(useCount); + Serial.print(F("/")); + Serial.print(USES_BEFORE_AUTO_CALIBRATION); + Serial.print(F(" autoPending=")); + Serial.print(autoCalibrationPending ? 1 : 0); + Serial.print(F(" forceCal=")); + Serial.print(forceCalibration ? 1 : 0); + Serial.print(F(" autoCalState=")); + Serial.println(autoCalibrationState); +} + +void setRelay(int relayPin, boolean state) { + digitalWrite(relayPin, state ? LOW : HIGH); +} + +void seat(int action) { + requestedSeatAction = action; +} + +void updateRelayOutput(unsigned long currentTime) { + if (requestedSeatAction == currentSeatAction) { + applySeatAction(currentSeatAction); + return; + } + + if (currentSeatAction != 0) { + lastStoppedSeatAction = currentSeatAction; + currentSeatAction = 0; + relayDeadtimeStartTime = currentTime; + applySeatAction(0); + return; + } + + bool sameDirectionResume = requestedSeatAction != 0 && requestedSeatAction == lastStoppedSeatAction; + bool deadtimeDone = currentTime - relayDeadtimeStartTime >= RELAY_DEADTIME_MS; + if (sameDirectionResume || deadtimeDone) { + currentSeatAction = requestedSeatAction; + } + + applySeatAction(currentSeatAction); +} + +void applySeatAction(int action) { + if (action == 1) { + setRelay(seatRelayD8, 1); + setRelay(seatRelayD9, 0); + } else if (action == -1) { + setRelay(seatRelayD8, 0); + setRelay(seatRelayD9, 1); + } else { + setRelay(seatRelayD8, 0); + setRelay(seatRelayD9, 0); + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..ec38194 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# Auto_Seat + +## Overview + +- Type: Arduino project +- Source directory: `Auto_Seat` +- Files: 3 +- Related classification: AutoSeat_UL, Auto_Seat_LL 계열과 관련된 자동 시트 프로젝트입니다. +- Report/reference file: `report.html` + +## Arduino Sketches + +- `Auto_Seat.ino` + +## Root Files + +- `Auto_Seat.ino` +- `report.html` +- `upload.ps1` + +## Notes + +- This repository is intended to be stored as a private project repository on `git.chaegeon.com`. +- Sensitive configuration may exist in source files and is kept private by repository visibility. +- Exact duplicate top-level project folders were checked before repository preparation. diff --git a/report.html b/report.html new file mode 100644 index 0000000..d6e3c77 --- /dev/null +++ b/report.html @@ -0,0 +1,305 @@ + + + + + + Auto Seat 동작 시뮬레이션 보고서 + + + +
+

Auto Seat 동작 시뮬레이션 보고서

+
대상 파일: Auto_Seat.ino / 현재 코드 기준
+
+ +
+

1. 현재 핵심 설정

+
+
+ 최대 상승 가능 위치 + SEAT_HARD_LIMIT_UP_MS = 6000 +

모든 자동/수동 위치 목표는 0..6000ms 범위 안에서만 허용됩니다.

+
+
+ 자동 상승 위치 + storedSeatPositionMs = EEPROM +

하드코딩 기본 위치는 사용하지 않고 EEPROM에서 읽습니다. EEPROM 초기화 시 6000ms로 설정됩니다.

+
+
+ 하강 속도 추정 + 11 / 10 +

일반 자동 하강과 수동 하강 위치 계산은 상승보다 1.1배 빠른 것으로 추정합니다.

+
+
+ 수동 스텝 + MANUAL_STEP_MS = 500 +

u는 +500ms, d는 -500ms 이동 목표를 설정합니다.

+
+
+ 자동 보정 + UP 7000ms -> DOWN 6000ms +

10회 사용 후 시동 OFF 상태 5분 경과 시 자동 보정합니다. c 명령은 즉시 보정합니다.

+
+
+ 릴레이 보호 + RELAY_DEADTIME_MS = 120 +

상승/하강 반전 시 릴레이를 잠깐 OFF로 두어 접점 부담을 줄입니다.

+
+
+ +

2. 입력 신호 판정

+ + + + + + + + + + + + +
항목조건결과
P ONparkingRawValue >= 950parkingState = HIGH
P OFFparkingRawValue <= 940parkingState = LOW
주행 rawIG1 HIGH && P LOW시트 상승 목표 상태
주행 확정주행 raw가 80ms 유지confirmedDriveMode = HIGH
P/비주행 확정비주행 raw가 250ms 유지confirmedDriveMode = LOW
시동 직후 안정화IG1 LOW -> HIGH 후 1000ms릴레이 OFF, P 신호 튐 무시
+ +

3. 장시간 주차 후 전원 인가부터 시뮬레이션

+

3.1 잠금 해제 직후

+
IG1 = LOW
+P = LOW
+confirmedDriveMode = LOW
+targetPosMs = 0
+relay = OFF
+

사용자 설명 기준으로 잠금 해제 직후에는 IG1 OFF, P OFF입니다. 코드는 주행 조건을 IG1 HIGH && P LOW로 보므로 상승하지 않습니다.

+ +

3.2 브레이크 후 시동 ON 직후

+
IG1 LOW -> HIGH
+isStabilizing = true
+for 1000ms:
+  seat(0)
+  relay = OFF
+

시동 직후 P가 약 1초 동안 ON/OFF/ON으로 흔들려도 안정화 구간에서 릴레이는 동작하지 않습니다.

+ +

3.3 P단 유지

+
IG1 = HIGH
+P = HIGH
+confirmedDriveMode = LOW
+targetPosMs = 0
+

P단에서는 목표 위치가 0입니다. 현재 추정 위치가 0보다 높으면 하강합니다.

+ +

3.4 P단 외 이동

+
P = LOW for 80ms
+confirmedDriveMode = HIGH
+targetPosMs = storedSeatPositionMs
+relay = UP until currentPosMs reaches targetPosMs
+

P단 외 상태가 80ms 유지되면 EEPROM에 저장된 위치까지 상승합니다.

+ +

3.5 P단 복귀 또는 시동 OFF

+
confirmedDriveMode = LOW
+targetPosMs = 0
+currentPosMs -= deltaTime * 11 / 10
+when currentPosMs <= 0:
+  currentPosMs = 0
+  DOWN relay extra 300ms
+  relay = OFF
+

하강은 1.1배 속도로 위치를 추정하고, 바닥 도달 후 300ms 추가 하강으로 바닥 밀착감을 줍니다.

+ +

4. 한 글자 시리얼 명령

+

시리얼 연결 후 처음에는 잠금 상태입니다. 빈 Enter를 한 번 입력하면 도움말과 현재 상태를 출력하고, 그때부터 한 글자 명령을 사용할 수 있습니다. 잠금 해제 후 Enter 단독 입력은 아무 동작도 하지 않습니다. 안전을 위해 p/u/d/a/c/r 명령은 P단에서만 실행됩니다.

+ + + + + + + + + + + + + + + + +
명령동작응답
s현재 상태 1회 출력상태 ACK 출력
u현재 추정 위치에서 +500ms 목표 설정현재 위치/목표 출력
d현재 추정 위치에서 -500ms 목표 설정현재 위치/목표 출력
p현재 추정 위치를 EEPROM 자동 상승 위치로 저장저장된 위치 출력
g1초마다 debug 출력 시작debug 중에는 x만 작동
xdebug 모드 종료 전용debug 종료 후 상태 출력
a수동 모드 해제, 기어 기반 자동 목표로 복귀상태 출력
c즉시 보정: 상승 7초, 하강 6초, 위치 0 설정상태 출력
r10회 자동 보정 카운터 초기화상태 출력
h도움말 출력도움말 + 현재 상태 출력
+

주의: 명령은 한 글자만 받습니다. debug, down, position 2000처럼 긴 문자열은 실행하지 않습니다.

+ +

5. 수동 위치 세팅 시뮬레이션

+ + + + + + + + + + + +
현재 추정 위치입력계산결과
2000u2000 + 500 = 2500목표 2500으로 상승, 상태 출력
2500u2500 + 500 = 3000목표 3000으로 상승, 상태 출력
3000p현재 위치 3000 저장EEPROM 저장 위치가 3000으로 변경
3000P단 복귀목표 0하강
0P단 외 이동목표 EEPROM 30003000까지 자동 상승
+ +

6. 범위 초과 시뮬레이션

+ + + + + + + + + +
현재 추정 위치입력계산결과
5800u63000..6000 범위 초과, 경고 출력, 목표 변경 없음
200d-3000..6000 범위 초과, 경고 출력, 목표 변경 없음
임의abc한 글자 아님실행 거절, ACK 상태 출력
+ +

7. 10회 사용 후 자동 보정 시뮬레이션

+
each IG1 HIGH -> LOW:
+  useCount++
+  EEPROM.update(useCount)
+
+if useCount >= 10 and IG1 LOW for 5 minutes:
+  UP relay ON   for 7000ms
+  DOWN relay ON for 6000ms
+  relay OFF
+  currentPosMs = 0
+  targetPosMs = 0
+  useCount = 0
+

자동 보정 중 시동이 켜지면 중단합니다. 단, c로 시작한 강제 보정은 IG1 ON 상태에서도 끝까지 진행합니다.

+ +

8. 현재 주의 사항

+
    +
  • 시트 위치는 실제 센서가 아니라 시간 기반 추정값입니다.
  • +
  • 전원 차단 후 재부팅하면 currentPosMs는 0에서 시작합니다.
  • +
  • u/d는 위치 조절만 하고 EEPROM 저장은 하지 않습니다. 저장은 반드시 p로 합니다.
  • +
  • d는 하강, g는 debug입니다. debug 충돌을 피하기 위해 d를 debug로 쓰지 않습니다.
  • +
  • debug 상태에서는 x만 작동하며, r, a, s, u, d, p, c는 모두 무시됩니다.
  • +
+ +

9. 추가 예외처리 기준

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
예외 상황처리목적
시트 이동 중 키 입력ERR: busy 출력 후 명령 무시연속 입력으로 500ms 단위가 흐트러지는 문제 방지
P단이 아닌 상태에서 p/u/d/a/c/r 입력ERR: check gear state. Command requires P. 출력수동 이동, 저장, 보정, EEPROM 변경이 P단 외 상태에서 실행되는 문제 방지
수동 이동 완료 전 기어 상태 변경수동 완료 대기 플래그 해제자동 제어로 넘어간 뒤 잘못된 MANUAL_STEP_DONE 출력 방지
내부 위치값 범위 이탈currentMs, targetMs를 0..6000으로 보정시간 계산 오차나 상태 꼬임으로 인한 비정상 목표 방지
보정 시작/중단수동 이동 플래그, 하강 잔여값, 하단 추가 하강 상태 초기화보정과 일반 이동 상태가 섞이는 문제 방지
+
+ + diff --git a/upload.ps1 b/upload.ps1 new file mode 100644 index 0000000..f57d115 --- /dev/null +++ b/upload.ps1 @@ -0,0 +1,13 @@ +param( + [string]$Port = "COM3", + [string]$Fqbn = "arduino:avr:nano:cpu=atmega328" +) + +$ArduinoCli = "arduino-cli" +$InstalledCli = "C:\Program Files\Arduino CLI\arduino-cli.exe" + +if (Test-Path $InstalledCli) { + $ArduinoCli = $InstalledCli +} + +& $ArduinoCli upload --fqbn $Fqbn --port $Port .