Add console CAN controller node

This commit is contained in:
seo-amugae committed 2026-08-23 19:27:12 +09:00
1 parent a130726907
commit 99a1850a21
3 files changed
+1366 -99

No files matched your search

+301 -99
View File
@@ -1,40 +1,117 @@
# 차량 승하차 편의를 위한 자동 시트 조절기
# Auto Seat
Arduino Nano와 2채널 릴레이를 이용해 순정 이지억세스가 없는 차량에 자동 시트 이동 기능을 붙이는 개인 프로젝트입니다.
차량 운전석 자동 시트 위치 제어와 센터콘솔 메모리 기능을 관리하는 Arduino 프로젝트입니다.
시동 상태와 P단 신호를 읽어 주행 상태가 되면 시트를 저장된 주행 위치까지 올리고, P단 또는 하차 상태가 되면 시트를 아래 위치까지 내립니다. 실제 위치 센서는 없으며, 이동 시간을 기준으로 현재 위치를 추정합니다.
운전석 아래의 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
- 릴레이: 2채널 Active-Low 릴레이 모듈
- 상승 릴레이: `D8`, `LOW = ON`
- 하강 릴레이: `D9`, `LOW = ON`
- 상승 릴레이: `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 핀은 충돌하지 않습니다.
- `IG1 HIGH`이고 P단 신호가 꺼져 있으면 80ms 확인 뒤 주행 모드로 봅니다.
- 주행 모드에서는 EEPROM에 저장된 주행 위치까지 시트를 올립니다.
- P단 또는 비주행 모드가 250ms 동안 유지되면 시트를 `0ms` 위치까지 내립니다.
- 하강 속도는 상승보다 약 `1.1배` 빠른 것으로 계산합니다.
- 추정 위치가 `0ms`에 도달하면 하단 끝단 보정을 위해 하강 릴레이를 300ms 더 유지합니다.
- IG1이 켜진 직후에는 신호 튐을 피하기 위해 1000ms 동안 릴레이 출력을 끕니다.
## 센터콘솔 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`
- 시트 주행 위치는 EEPROM에 저장합니다.
- 허용 범위는 `0..6000ms`입니다.
- EEPROM magic 값이 없거나 저장값이 유효하지 않으면 기본 주행 위치를 `6000ms`로 둡니다.
- serial 명령 `p`로 현재 추정 위치를 EEPROM에 저장합니다.
- 보정 명령 `c`는 현재 위치를 다시 맞출 뿐, 저장된 주행 위치를 바꾸지 않습니다.
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 serial 연결 때 DTR 때문에 리셋될 수 있습니다.
Arduino Nano는 USB 시리얼 연결 시 DTR 때문에 리셋될 수 있습니다.
보드가 부팅되는 순간 이미 `IG1 HIGH`이고 P단이 아니라면, 차가 주행 위치에 있다고 보고 현재 위치와 목표 위치를 EEPROM 저장 위치로 맞춥니다.
보드가 부팅된 순간 `IG1 HIGH`이고 P단이 아니면, 이미 주행 중 USB 재연결이 발생한 것으로 보고 현재 위치와 목표 위치를 EEPROM 저장 주행 위치로 맞춥니다.
```text
currentPositionMs = savedPositionMs
@@ -42,18 +119,154 @@ targetPositionMs = savedPositionMs
relay = OFF
```
이렇게 해서 주행 중 USB 재연결이나 serial monitor 열림 때문에 시트가 불필요하게 위로 움직이는 상황을 막습니다. 이후 기어를 P단으로 옮기면 저장된 위치 추정값에서 `0ms`까지 내려갑니다.
이렇게 해서 주행 중 시리얼 연결만으로 시트가 불필요하게 상승하는 상황을 막습니다. 이후 사용자가 P단으로 이동하면 저장 위치 기준으로 `0ms`까지 하강합니다.
## Serial Console
## 시트 EEPROM
Serial은 기본적으로 조용한 상태입니다.
```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으로 직접 누른 버튼은 일정 시간 마스킹하여 사용자 직접 조작으로 오인하지 않습니다.
## Nano Serial Console
Nano 시리얼은 기본적으로 조용합니다.
1. `115200` baud로 연결합니다.
2. 빈 Enter를 한 번 보내면 help가 출력되고 콘솔이 열립니다.
3. 이후에는 한 글자 명령만 받습니다.
명령:
```text
s 현재 상태 출력
u 수동 상승 +500ms
@@ -62,80 +275,43 @@ p 현재 추정 위치를 EEPROM에 저장
g 1초 간격 debug 출력 시작
x debug 모드만 종료
a 기어 상태 기반 자동 목표로 복귀
c 보정 실행: 7초 상승, 6초 하강, 현재 위치 0으로 설정
c 보정 실행: 상승 7초, 하강 6초, 현재 위치 0 설정
r 10회 자동 보정 카운터 초기화
h help 출력
```
다음 관리 명령은 안전을 위해 P단에서만 실행됩니다.
`a`, `c`, `r`은 안전을 위해 P단에서만 실행됩니다.
상태 출력에는 센터콘솔 상태도 함께 포함됩니다.
```text
a c r
consoleCanReady=true
consoleAlive=true
consoleBusy=false
consolePulseQueueLength=0
consoleDriverVentTarget=0
consoleDriverVentActual=0
consoleSteeringHeatTarget=false
consoleSteeringHeatActual=false
```
P단이 아니면 다음처럼 거부합니다.
## Mega Serial Console
Mega 시리얼은 I/O 노드 디버그용입니다.
```text
ERR: check gear state. Command requires P.
h help 출력
s 상태와 ledRaw0..ledRaw14 출력
x 모든 출력 OFF, pulse queue 비움
```
debug 모드 중에는 `x`만 받으며 다른 입력은 무시합니다.
## 상태 출력
상태 출력은 한 줄에 한 항목씩 표시하고, 부팅 후 경과 시간을 포함합니다.
```text
[STATUS]
uptime=0h 2m 13s 457ms
parkingRawValue=1023
ignition=true
parking=true
rawDriveMode=false
confirmedDriveMode=false
```
자동 보정 카운터가 10회에 도달한 상태라면 IG1 OFF 유지 시간을 함께 보여줍니다.
```text
autoCalibrationOffTimer=elapsed 0h 1m 20s 123ms remaining 0h 3m 39s 877ms
```
IG1이 아직 켜져 있으면 다음처럼 표시합니다.
```text
autoCalibrationOffTimer=waiting_for_ig_off
```
debug 모드는 1초마다 읽기 쉬운 블록을 출력합니다.
```text
[DEBUG]
uptime=0h 0m 5s 123ms
parkingRawValue=1023
ignition=true
parking=true
rawDriveMode=false
confirmedDriveMode=false
currentPositionMs=0
targetPositionMs=0
savedPositionMs=6000
hardLimitPositionMs=6000
requestedRelayAction=0
actualRelayAction=0
manualMode=false
stabilizing=false
useCount=0/10
autoCalibrationPending=false
forceCalibration=false
autoCalibrationState=0
```
Mega에는 EEPROM 메모리나 자동 판단 로직이 없습니다. Mega가 리셋되어도 Nano가 다음 상태 보고를 받은 뒤 필요한 명령을 다시 보냅니다.
## 자동 보정
- `IG1 HIGH -> LOW` 전환이 발생할 때마다 EEPROM 사용 카운터를 1 올립니다.
- `IG1 HIGH -> LOW` 전환이 발생할 때마다 EEPROM useCount를 1 올립니다.
- 10회가 되면 자동 보정을 예약합니다.
- IG1이 꺼진 상태로 5분 유지되면 자동 보정이 실행됩니다.
- IG1 OFF 상태가 5분 유지되면 자동 보정을 실행합니다.
```text
up 7000ms
@@ -145,32 +321,58 @@ targetPositionMs = 0
useCount = 0
```
예약 상태에서 IG1이 켜지면 보정은 취소됩니다. 수동 `c` 보정은 P단에서만 가능하고, 시작되면 끝까지 수행합니다.
## 안전 장치
- 시트가 움직이는 중이거나 릴레이 전환 대기 중이면 관리 명령을 거부합니다.
- 내부 위치값은 항상 `0..6000ms`로 제한합니다.
- 릴레이 방향 전환 사이에는 120ms dead time을 둡니다.
- 수동 이동 중 기어 상태가 바뀌면 수동 step 완료 처리를 취소합니다.
- 실수로 EEPROM을 초기화하지 않도록 factory reset 명령은 제거했습니다.
예약 상태에서 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 컴파일:
P단 감지 임계값은 현재 코드 기준으로 ON `950`, OFF `940` hysteresis를 사용합니다. 차량별 전압 분배와 신호 특성이 다를 수 있으므로 실제 `analogRead` 값을 확인한 뒤 임계값을 조정해야 합니다.
```powershell
& "C:\Program Files\Arduino CLI\arduino-cli.exe" compile --fqbn arduino:avr:mega .\Console_Node --warnings all
```
이 프로젝트는 물리 위치 센서 없이 시간으로 위치를 추정합니다. IG1 OFF/P 상태에서 전원이 끊기면 다음 부팅은 하단 위치 추정값 `0ms`에서 시작합니다. 반대로 이미 주행 상태에서 보드가 리셋되면 EEPROM에 저장된 주행 위치에서 시작해 USB 재연결로 인한 불필요한 상승 동작을 줄입니다.
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
```