Files
Auto_Seat/README.md
T

480 lines
14 KiB
Markdown

# Auto Seat
차량 운전석 자동 시트 위치 제어와 센터콘솔 메모리 기능을 관리하는 Arduino 프로젝트입니다.
운전석 아래의 Arduino Nano가 메인 컨트롤러입니다. 기존 시트 상승/하강 제어, EEPROM 저장값, 시리얼 콘솔, IG1/P단 판단을 담당합니다. 센터콘솔 쪽 Arduino Mega는 단순 I/O 노드로 동작하며, CAN 명령을 받아 MOSFET 출력으로 버튼을 누르고 버튼/STATE_LED 상태를 Nano에 보고합니다.
## 목적
- P단이 아닌 주행 상태가 확인되면 시트를 EEPROM에 저장된 주행 위치까지 상승시킵니다.
- P단 또는 비주행 상태가 확인되면 시트를 하단 `0ms` 위치까지 하강시킵니다.
- 실제 위치 센서 없이 릴레이 구동 시간으로 현재 위치를 추정합니다.
- 운전석/조수석 열선과 통풍, 핸들 열선, 전방센서, VESS 보조 기능을 센터콘솔 노드로 제어합니다.
- 모든 메모리와 판단은 Nano가 담당하고, Mega는 신호 감지와 출력 수행만 담당합니다.
## 저장소 구성
```text
Auto_Seat.ino
Console_Node/Console_Node.ino
upload.ps1
report.html
README.md
```
- `Auto_Seat.ino`: 운전석 아래 Nano용 메인 스케치입니다.
- `Console_Node/Console_Node.ino`: 센터콘솔 Mega용 I/O 노드 스케치입니다.
- `upload.ps1`: Nano 업로드용 PowerShell 스크립트입니다.
- `report.html`: 현재 동작 시뮬레이션과 운영 보고서입니다.
## 메인 Nano 하드웨어
- MCU: Arduino Nano ATmega328P
- 상승 릴레이: `D8`, active-low, `LOW = ON`
- 하강 릴레이: `D9`, active-low, `LOW = ON`
- P단 입력: `A0`, analog raw `0..1023`
- IG1 입력: `D7`, `HIGH = IG1 ON`, `LOW = IG1 OFF`
- CAN 모듈: MCP2515 + TJA1050, 8MHz crystal
- CAN CS: `D10`
- CAN SPI: `D11 MOSI`, `D12 MISO`, `D13 SCK`
Nano의 기존 시트 제어 핀과 MCP2515 SPI 핀은 충돌하지 않습니다.
## 센터콘솔 Mega 하드웨어
- MCU: Arduino Mega
- CAN 모듈: MCP2515 + TJA1050, 8MHz crystal
- CAN CS: `D53`
- CAN INT: `D2`
- CAN SPI: `D50 MISO`, `D51 MOSI`, `D52 SCK`
- MOSFET 출력 7채널: `D22..D28`
- 버튼 눌림 감지 7채널: `D30..D36`
- STATE_LED analog 입력 15채널: `A0..A14`
MOSFET 출력은 YYNMOS-8 같은 low-side 보드를 기준으로 합니다. 현재 설정은 `HIGH = ON`, `LOW = OFF`입니다. 보드가 반전형이면 `OUTPUT_ACTIVE_LEVEL`, `OUTPUT_INACTIVE_LEVEL`만 바꾸면 됩니다.
PC817 출력 모듈은 보통 active-low입니다. 버튼 감지 쪽은 기본값이 `INPUT_PULLUP`, `BUTTON_ACTIVE_LOW = true`입니다. 실제 모듈 출력이 반대면 배열 값만 바꿉니다.
## 센터콘솔 채널
```text
0 = 운전석 통풍
1 = 운전석 열선
2 = 조수석 통풍
3 = 조수석 열선
4 = 핸들 열선
5 = 전방센서
6 = VESS
```
열선/통풍 4채널은 단계형입니다.
```text
0 -> 3 -> 2 -> 1 -> 0
```
일반 클릭은 `300ms`입니다. 열선/통풍 OFF 기준화는 해당 버튼을 `2100ms` 길게 눌러 처리합니다.
## STATE_LED 입력
Mega는 STATE_LED 15개를 analogRead로 읽고, 단계와 ON/OFF 상태만 Nano에 보고합니다.
```text
A0..A2 운전석 통풍 High/Mid/Low
A3..A5 운전석 열선 High/Mid/Low
A6..A8 조수석 통풍 High/Mid/Low
A9..A11 조수석 열선 High/Mid/Low
A12 핸들 열선
A13 전방센서
A14 VESS
```
기본 임계값은 전 채널 `300`입니다. 5V ADC 기준으로 1.8V는 약 `368`, 3.3V는 약 `675`입니다. 실제 배선 후 Mega 시리얼의 `s` 명령으로 `ledRaw0..ledRaw14` 값을 확인하고 `LED_ON_THRESHOLD`를 조정합니다.
PC817 출력처럼 LED ON일 때 analog 값이 낮아지는 채널은 `LED_ON_WHEN_ABOVE`를 `false`로 바꿉니다.
## 시트 동작 로직
- `IG1 HIGH`이고 P단 신호가 꺼져 있으면 주행 후보 상태로 봅니다.
- 주행 후보가 `80ms` 유지되면 주행 모드로 확정합니다.
- P단 또는 비주행 후보가 `250ms` 유지되면 비주행 모드로 확정합니다.
- 시동 직후 `1000ms` 동안은 IG1/P 신호 흔들림을 무시하고 릴레이를 정지합니다.
- 주행 모드에서는 EEPROM에 저장된 시트 위치로 상승합니다.
- 비주행 모드에서는 `0ms` 위치로 하강합니다.
- 하강 속도는 상승보다 `1.1배` 빠른 것으로 계산합니다.
- `0ms` 도달 시 하단 밀착을 위해 하강 릴레이를 추가 `300ms` 유지합니다.
- 릴레이 방향 전환 사이에는 `120ms` dead time을 둡니다.
시트 위치는 실제 센서값이 아니라 시간 기반 추정값입니다. 전원 차단 후 다시 켜지면 기본적으로 하단 `0ms`에서 시작한다고 봅니다.
## USB Serial Reset 보호
Arduino Nano는 USB 시리얼 연결 시 DTR 때문에 리셋될 수 있습니다.
보드가 부팅된 순간 `IG1 HIGH`이고 P단이 아니면, 이미 주행 중 USB 재연결이 발생한 것으로 보고 현재 위치와 목표 위치를 EEPROM 저장 주행 위치로 맞춥니다.
```text
currentPositionMs = savedPositionMs
targetPositionMs = savedPositionMs
relay = OFF
```
이렇게 해서 주행 중 시리얼 연결만으로 시트가 불필요하게 상승하는 상황을 막습니다. 이후 사용자가 P단으로 이동하면 저장 위치 기준으로 `0ms`까지 하강합니다.
## 시트 EEPROM
```text
0 = magic
1 = 10회 보정 useCount
2..3 = 시트 주행 위치 uint16_t
```
시트 위치 허용 범위는 `0..6000ms`입니다. EEPROM magic이 없거나 저장값이 유효하지 않으면 기본 주행 위치를 `6000ms`로 설정합니다.
## 센터콘솔 EEPROM
센터콘솔 목표값은 Mega가 아니라 Nano EEPROM에 저장합니다.
```text
16 = console magic
17 = 운전석 통풍 목표 단계 0..3
18 = 운전석 열선 목표 단계 0..3
19 = 조수석 통풍 목표 단계 0..3
20 = 조수석 열선 목표 단계 0..3
21 = 핸들 열선 목표 0/1
22 = 전방센서 목표 0/1
23 = VESS 목표 0/1
```
운전석 통풍이 켜지면 운전석 열선 메모리는 `0`으로 지웁니다. 운전석 열선이 켜지면 운전석 통풍 메모리는 `0`으로 지웁니다. 조수석도 같은 규칙을 적용합니다.
## 센터콘솔 자동 적용
- Mega가 CAN 상태를 보내고 Nano가 alive로 인식하면 센터콘솔 기능을 사용할 수 있습니다.
- 부팅 후 `3000ms`가 지나고 Mega가 준비되어 있으면 저장된 열선/통풍 목표를 적용합니다.
- 핸들 열선, 전방센서, VESS는 IG1이 켜진 뒤에만 목표값을 적용합니다.
- 열선/통풍 목표가 `1..3`이면 약 `19분 30초`마다 저장 목표를 다시 적용합니다.
- 핸들 열선 목표가 ON이고 IG1이 ON이면 약 `19분 30초`마다 OFF 클릭 후 다시 ON 클릭을 수행합니다.
- Mega가 바쁘거나 CAN 응답이 없으면 Nano는 새 명령을 보내지 않습니다.
사용자가 센터콘솔 버튼을 직접 누르면 Mega가 눌림/릴리즈를 Nano에 보고합니다. Nano는 릴리즈 후 `700ms` 뒤 실제 LED 상태를 읽어 EEPROM 목표값을 갱신합니다.
## CAN 프로토콜
속도는 `125kbps`, MCP2515 crystal은 `8MHz`로 설정합니다.
```text
0x100 Nano -> Mega heartbeat
0x101 Nano -> Mega command
0x200 Mega -> Nano status
```
### 0x100 Heartbeat
```text
byte0 = protocol version, 1
byte1 = flags
byte2 = sequence
byte3 = useCount
byte4 = parking raw low
byte5 = parking raw high
byte6 = uptime seconds low
byte7 = uptime seconds high
```
flags:
```text
bit0 = IG1 ON
bit1 = P ON
bit2 = raw drive mode
bit3 = confirmed drive mode
bit4 = ignition stabilizing
bit5 = seat moving
bit6 = auto calibration pending/running
```
### 0x101 Command
현재 명령은 pulse 실행입니다.
```text
byte0 = protocol version, 1
byte1 = command, 1 pulse
byte2 = channel 0..6
byte3 = onMs low
byte4 = onMs high
byte5 = gapMs low
byte6 = gapMs high
byte7 = command sequence
```
Mega는 같은 sequence를 중복 수신하면 무시합니다. 명령을 수락하면 status 프레임의 `lastAcceptedCommandSeq`로 돌려줍니다.
### 0x200 Status
```text
byte0 = protocol version, 1
byte1 = flags
byte2 = packed climate levels
byte3 = toggle bits
byte4 = button bits
byte5 = lastAcceptedCommandSeq
byte6 = Mega pulse queue length
byte7 = last main heartbeat sequence
```
flags:
```text
bit0 = main alive
bit1 = Mega pulse busy
bit2 = Mega pulse queue overflow
bit3 = main IG1 ON
bit4 = main P ON
```
packed climate levels:
```text
bits 0..1 = 운전석 통풍 0..3
bits 2..3 = 운전석 열선 0..3
bits 4..5 = 조수석 통풍 0..3
bits 6..7 = 조수석 열선 0..3
```
toggle bits:
```text
bit0 = 핸들 열선
bit1 = 전방센서
bit2 = VESS
```
button bits:
```text
bit0..bit6 = 센터콘솔 버튼 눌림 상태
```
Mega가 MOSFET으로 직접 누른 버튼은 일정 시간 마스킹하여 사용자 직접 조작으로 오인하지 않습니다.
### 실제 패킷 예시
멀티바이트 시간값은 little-endian입니다. 예를 들어 `300ms`는 `0x012C`이므로 payload에는 `2C 01` 순서로 들어갑니다.
주행 중 Nano heartbeat 예시:
```text
ID : 0x100
DLC : 8
DATA: 01 2D 2A 05 14 00 7B 00
```
해석:
```text
byte0 = 0x01 protocol version 1
byte1 = 0x2D IG1 ON, raw drive ON, confirmed drive ON, seat moving
byte2 = 0x2A heartbeat sequence 42
byte3 = 0x05 useCount 5
byte4 = 0x14 parking raw low
byte5 = 0x00 parking raw high, parkingRawValue = 20
byte6 = 0x7B uptime seconds low
byte7 = 0x00 uptime seconds high, uptime = 123s
```
운전석 통풍 버튼을 300ms 클릭하라는 명령 예시:
```text
ID : 0x101
DLC : 8
DATA: 01 01 00 2C 01 C2 01 31
```
해석:
```text
byte0 = 0x01 protocol version 1
byte1 = 0x01 pulse command
byte2 = 0x00 channel 0, driver vent
byte3 = 0x2C onMs low
byte4 = 0x01 onMs high, onMs = 300
byte5 = 0xC2 gapMs low
byte6 = 0x01 gapMs high, gapMs = 450
byte7 = 0x31 command sequence 49
```
운전석 열선을 길게 눌러 OFF 기준화하는 명령 예시:
```text
ID : 0x101
DLC : 8
DATA: 01 01 01 34 08 BC 02 32
```
해석:
```text
channel = 1, driver heat
onMs = 0x0834 = 2100ms
gapMs = 0x02BC = 700ms
sequence = 0x32
```
핸들 열선 20분 유지 재시작은 두 개의 pulse 명령으로 보냅니다.
```text
ID : 0x101
DATA: 01 01 04 2C 01 B0 04 33
ID : 0x101
DATA: 01 01 04 2C 01 C2 01 34
```
해석:
```text
첫 번째 frame: channel 4를 300ms 누르고 1200ms 대기
두 번째 frame: channel 4를 300ms 다시 누르고 450ms 대기
```
Mega status 예시:
```text
ID : 0x200
DLC : 8
DATA: 01 0B 83 05 01 31 02 2A
```
해석:
```text
byte0 = 0x01 protocol version 1
byte1 = 0x0B main alive, Mega busy, main IG1 ON
byte2 = 0x83 driver vent 3, driver heat 0, passenger vent 0, passenger heat 2
byte3 = 0x05 steering heat ON, front sensor OFF, VESS ON
byte4 = 0x01 channel 0 button pressed
byte5 = 0x31 last accepted command sequence 49
byte6 = 0x02 Mega pulse queue length 2
byte7 = 0x2A last received Nano heartbeat sequence 42
```
## Nano Serial Console
Nano 시리얼은 기본적으로 조용합니다.
1. `115200` baud로 연결합니다.
2. 빈 Enter를 한 번 보내면 help가 출력되고 콘솔이 열립니다.
3. 이후에는 한 글자 명령만 받습니다.
```text
s 현재 상태 출력
u 수동 상승 +500ms
d 수동 하강 -500ms
p 현재 추정 위치를 EEPROM에 저장
g 1초 간격 debug 출력 시작
x debug 모드만 종료
a 기어 상태 기반 자동 목표로 복귀
c 보정 실행: 상승 7초, 하강 6초, 현재 위치 0 설정
r 10회 자동 보정 카운터 초기화
h help 출력
```
`a`, `c`, `r`은 안전을 위해 P단에서만 실행됩니다.
상태 출력에는 센터콘솔 상태도 함께 포함됩니다.
```text
consoleCanReady=true
consoleAlive=true
consoleBusy=false
consolePulseQueueLength=0
consoleDriverVentTarget=0
consoleDriverVentActual=0
consoleSteeringHeatTarget=false
consoleSteeringHeatActual=false
```
## Mega Serial Console
Mega 시리얼은 I/O 노드 디버그용입니다.
```text
h help 출력
s 상태와 ledRaw0..ledRaw14 출력
x 모든 출력 OFF, pulse queue 비움
```
Mega에는 EEPROM 메모리나 자동 판단 로직이 없습니다. Mega가 리셋되어도 Nano가 다음 상태 보고를 받은 뒤 필요한 명령을 다시 보냅니다.
## 자동 보정
- `IG1 HIGH -> LOW` 전환이 발생할 때마다 EEPROM useCount를 1 올립니다.
- 10회가 되면 자동 보정을 예약합니다.
- IG1 OFF 상태가 5분 유지되면 자동 보정을 실행합니다.
```text
up 7000ms
down 6000ms
currentPositionMs = 0
targetPositionMs = 0
useCount = 0
```
예약 상태에서 IG1이 다시 켜지면 보정은 대기 상태로 남고, 다음 IG1 OFF 후 5분 조건을 다시 봅니다. 수동 `c` 보정은 P단에서만 가능하고, 시작되면 끝까지 수행합니다.
## 빌드와 업로드
Nano 컴파일:
```powershell
& "C:\Program Files\Arduino CLI\arduino-cli.exe" compile --fqbn arduino:avr:nano:cpu=atmega328 --warnings all .
```
Nano 업로드:
```powershell
.\upload.ps1 -Port COM3
```
Mega 컴파일:
```powershell
& "C:\Program Files\Arduino CLI\arduino-cli.exe" compile --fqbn arduino:avr:mega .\Console_Node --warnings all
```
Mega 업로드 예시:
```powershell
& "C:\Program Files\Arduino CLI\arduino-cli.exe" upload --fqbn arduino:avr:mega --port COM4 .\Console_Node
```
## 운영 메모
- 커밋 전 `git status --short --branch`로 의도하지 않은 파일이 섞였는지 확인합니다.
- MCP2515 모듈의 crystal은 현재 `8MHz` 기준입니다. 16MHz 모듈이면 `MCP_8MHZ`를 바꿔야 합니다.
- CAN 종단저항은 노드가 2개일 때 양 끝 120Ω 구성을 기준으로 합니다.
- 센터콘솔 CAN 상태가 2초 이상 끊기면 Mega는 출력 OFF와 큐 초기화를 수행합니다.
- Nano가 Mega 상태를 2초 이상 받지 못하면 콘솔 명령 큐를 비우고 자동 적용을 다시 대기합니다.
- STATE_LED 임계값은 차량 실측 raw 값을 기준으로 조정해야 합니다.
- 시트 위치는 센서 기반이 아니라 시간 기반 추정이므로 전원 차단, 수동 조작, 릴레이 지연에 따라 실제 위치와 차이가 날 수 있습니다.
## 현재 기준값
```text
SEAT_HARD_LIMIT_UP_MS = 6000
DOWN_SPEED_NUM / DOWN_SPEED_DEN = 11 / 10
IGNITION_STABILIZE_MS = 1000
DRIVE_CONFIRM_MS = 80
PARK_CONFIRM_MS = 250
RELAY_DEADTIME_MS = 120
DOWN_EDGE_MARGIN_MS = 300
AUTO_CALIBRATION_DELAY_MS = 300000
AUTO_CALIBRATION_UP_MS = 7000
AUTO_CALIBRATION_DOWN_MS = 6000
CAN_HEARTBEAT_INTERVAL_MS = 250
CONSOLE_MEMORY_REFRESH_INTERVAL_MS = 1170000
CONSOLE_STEERING_RESTART_INTERVAL_MS = 1170000
```