Files
Auto_Seat/Auto_Seat.ino
T
2026-06-07 01:08:04 +09:00

666 lines
19 KiB
Arduino

//
// Auto_Seat
// Automatic seat controller with EEPROM settings and optional serial tools.
//
#include <EEPROM.h>
#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);
}
}