비상정지와 누전 입력 — NC 버튼, 즉시 차단, 그리고 해제 뒤 자동 재개 (M7-5)
M7-4에서 실제 EVerest와 BSP로 충전 한 사이클을 돌렸다. 이번에는 BSP에 안전 입력 두 개를 더했다.
| 신호 | 핀 | 입력 경로 | EVerest 오류 |
|---|---|---|---|
| EM_INT (비상정지) | PH14 = gpiochip7 14 |
J10 1번 → 포토커플러 TLP185 → 74LV14 | MREC8EmergencyStop |
| RCD_INT (누전) | PH15 = gpiochip7 15 |
RCD1 MD0630STA-P → 포토커플러 → 74LV14 | MREC2GroundFailure |
결과부터 정리하면 이렇다.
| 항목 | 결과 |
|---|---|
| 비상정지 입력 극성 | J10-1 연결 = High. 제품 버튼은 NC |
| 충전 중 비상정지 | 감지와 같은 ms에 K1 OFF, EVerest는 충전기를 사용 불가로 |
| 비상정지 해제 | 94 ms 뒤 EVerest가 충전을 자동 재개 → 해제 방식은 제품 요구사항으로 결정 필요 |
| 누전 | 코드는 넣었지만 시험 방법이 없어 극성 미검증 |
1. 먼저 핀 충돌부터
9월에 MYiR OS에서 겪은 일이 있다. U-Boot가 LCD를 초기화하면서 PH12·PH14·PH15를 LCD 출력(alternate function) 으로 잡은 채 커널에 넘겼다. 리눅스는 아무도 쓰지 않는 핀은 건드리지 않는다. 그래서 그 핀들이 74LV14 출력과 맞붙어 있었다. 지금 U-Boot는 새로 만든 것(DK1 기준, LCD 없음)이라 괜찮을 것 같았지만, 입력 시험 전에 실제 상태를 봤다.
$ grep -E '^pin 12[6-7] ' /sys/kernel/debug/pinctrl/*50002000*/pinconf-pins
pin 126 (PH14): analog
pin 127 (PH15): analog
analog는 STM32에서 아무도 쓰지 않는 핀의 기본 상태다. 디지털 입력 회로가 꺼져 있을 뿐이고, 출력이 아니다. gpioinfo가 보여 주는 input만으로는 이걸 판별할 수 없다(AF 모드도 input으로 표시된다). 그래서 pinconf-pins로 확인했다.
2. 극성 시험
$ gpioget -c gpiochip7 14 15
"14"=inactive "15"=inactive
$ gpiomon -c gpiochip7 -n 4 14
2790.522522330 rising gpiochip7 14 "PH14"
2790.930170830 falling gpiochip7 14 "PH14"
2791.726420914 rising gpiochip7 14 "PH14"
2791.738922872 falling gpiochip7 14 "PH14"
J10-1을 연결하면 rising(High), 떼면 falling(Low)이다. 두 번째 High는 12.5 ms로 아주 짧았다. 선이 닿는 순간의 접점 튐으로 보인다.
제품의 비상정지 버튼은 NC(평소 닫힘) 다. 평소에는 회로가 닫혀 있어 High이고, 누르면 열려서 Low가 된다.
| 상태 | 입력 |
|---|---|
| 버튼 안 누름 (닫힘) | High → 정상 |
| 버튼 누름 (열림) | Low → 비상정지 |
| 선이 끊김, 커넥터 빠짐 | Low → 비상정지 |
NC를 쓰는 이유가 마지막 줄이다. 배선이 끊기면 비상정지로 처리된다. 고장이 안전한 쪽으로 난다.
누전 쪽은 실제 누설 전류를 흘리거나 RCD 모듈의 시험 단자를 쓸 방법이 없었다. 평소 값(Low)만 확인했고, 회로상 검출 시 High일 것으로 보고 기본값을 정했다. 설정으로 바꿀 수 있게 했고, “미검증”으로 적어 두었다.
3. BSP 구현
3-1. 감지는 즉시, 해제는 천천히
| 동작 | |
|---|---|
| 이상 감지 | 한 번 읽히는 즉시 K1 OFF + raise_error |
| 해제 | 정상이 20 ms 동안 계속될 때 clear_error |
| 이상이 걸린 동안 | EVerest가 allow_power_on을 보내도 K1을 켜지 않음 |
| 입력을 읽지 못함 | 이상으로 처리 |
| MQTT 연결, EVerest 재시작 | 걸려 있는 오류를 다시 보고 |
M7-3의 릴레이 규칙과 같은 생각이다. 위험한 쪽으로 바뀌는 것은 확인 없이 바로, 안전한 쪽으로 돌아오는 것은 확인한 뒤에. 앞의 12.5 ms짜리 접점 튐 같은 것은 해제 쪽 디바운스가 걸러 준다.
3-2. “ACTIVE = 이상”으로 맞추기
libgpiod v2에는 라인마다 active_low 설정이 있다. 이걸 쓰면 극성과 상관없이 “ACTIVE = 이상 발생”으로 코드를 통일할 수 있다.
gpiod_line_settings_set_direction(ls, GPIOD_LINE_DIRECTION_INPUT);
gpiod_line_settings_set_active_low(ls, in->active_low); /* EM: true, RCD: false */
...
static int input_read(const struct safety_input *in)
{
int v = gpiod_line_request_get_value(in->req, in->line);
if (v < 0)
return -1; /* 읽기 실패 -> 호출한 쪽에서 이상으로 */
return v == GPIOD_LINE_VALUE_ACTIVE;
}
릴레이 조건에는 한 줄이 더해졌다.
bool want = connected && enabled && allow_power_on && cd && !any_fault();
극성, 핀, 사용 여부, 디바운스 시간은 모두 설정 파일(/etc/default/sc6-bsp)에 있다.
EM_ACTIVE=low # NC 버튼: Low 가 비상정지
RCD_ACTIVE=high # 미검증
INPUT_DEBOUNCE_MS=20
입력은 CP 판정 한 번(40샘플, 약 3 ms)마다 앞뒤로 두 번 읽는다. 루프 주기가 약 15 ms이니 입력이 바뀐 뒤 감지까지는 최대 15 ms 안팎이다.
4. 시험
4-1. 시작하자마자 비상정지
새 BSP를 띄우자마자 비상정지가 걸렸다.
10:27:10.634 sc6-bsp: 비상정지 EM_INT: 사용 /dev/gpiochip7:14, 이상 = Low
10:27:10.634 sc6-bsp: 비상정지 EM_INT 감지
10:27:10.644 sc6-bsp: → m2e/raise_error {"type":"MREC8EmergencyStop","message":"EM_INT line 14"}
...
10:27:18.273 sc6-bsp: ← cp_state_F true ← EVerest: 충전기 사용 불가
버튼을 누르지 않았는데 왜 걸렸을까. J10-1에 아무것도 연결하지 않은 상태였기 때문이다. NC 기준으로는 선이 끊긴 것과 같다. 코드가 맞게 동작한 것이다. BSP가 핀을 잡고 있으면 gpioget이 안 되는데, 그럴 때는 debugfs로 값을 볼 수 있다.
$ grep -E 'PH1[45]' /sys/kernel/debug/gpio
gpio-638 (PH14 |sc6-bsp-EM ) in lo ACTIVE LOW
gpio-639 (PH15 |sc6-bsp-RCD ) in lo
벤치에는 NC 버튼이 없었다. 시험은 선을 붙였다 떼는 방식으로 하기로 하고, 보드의 설정 파일만 극성을 반대로(EM_ACTIVE=high) 바꿨다. 이 경우 연결이 비상정지다. 다시 빌드할 필요는 없었다. 레이어의 기본값은 NC 기준(low) 그대로 두었다.
4-2. 충전 중 비상정지
처음 두 번은 순서가 꼬였다. 로그를 보니 J10-1을 붙였다 뗀 다음에 충전이 시작돼 있었다. 그래서 BSP 로그를 실시간으로 띄워 놓고, K1 ON을 눈으로 본 다음에 붙이게 했다. 보드의 grep은 BusyBox라 --line-buffered가 없다. 출력이 바로 터미널로 가면 필요 없으니 빼고 실행했다.
tail -n0 -f /tmp/bsp.log | grep -E '감지|해제|K1 O|CP . ->'
10:39:49.675 CP B -> C (High 6.01V, 21/40 샘플)
10:39:49.687 K1 ON (allow_power_on)
10:39:54.319 비상정지 EM_INT 감지 ← J10-1 연결
10:39:54.319 K1 OFF (비상정지 EM_INT) ← 같은 ms
10:39:55.626 창: ... 듀티 100.0%, 상태 C, K1 OFF, EM 이상
10:40:01.052 비상정지 EM_INT 해제 ← J10-1 분리
10:40:01.146 K1 ON (allow_power_on) ← 94 ms 뒤 다시 붙음
10:40:03.936 K1 OFF (CP 가 C/D 에서 벗어남)
감지한 그 ms에 K1이 떨어졌다. 비상정지가 걸린 6.7초 동안은 지그가 여전히 C(충전 요청)였는데도 K1이 다시 붙지 않았다. EVerest 쪽에서는 이렇게 보였다.
Cleared error of type evse_board_support/MREC8EmergencyStop
Cleared error of type evse_manager/Inoperative
EVSE IEC Charger state: EVSE Paused->PrepareCharging
EvseManager는 BSP의 비상정지를 받으면 자기 쪽에 Inoperative(사용 불가) 오류를 건다. PWM을 끄고 CP를 +12V로 고정한다(로그의 듀티 100%). 해제되면 같이 푼다.
4-3. 해제하면 저절로 다시 충전한다
마지막 줄이 문제다. 비상정지를 풀자 아무 조작 없이 94 ms 만에 충전이 다시 시작됐다. 지그가 C 그대로였기 때문이다. EVerest 기본 동작이고 BSP 코드도 설계대로 움직였다.
그런데 실제 충전기에서 이게 맞을까. 비상정지는 사람이 위험을 보고 누르는 것이다. 보통은 버튼을 풀어도 사람이 다시 시작해야 재개되게 한다. 그렇다면 무엇으로 다시 시작할지를 정해야 한다.
| 방식 | BSP 변경 |
|---|---|
| A. 지금처럼 자동 재개 | 없음 |
| B. 케이블을 뽑아야(상태 A) 해제 | 비상정지 오류를 상태 A가 될 때까지 유지 |
| C. 별도 조작 (재시작 버튼, 카드, OCPP) | 해제 신호가 필요 |
코드는 어느 쪽이든 몇 줄이면 된다. 다만 어느 쪽이 맞는지는 코드가 아니라 제품 안전 요구사항으로 정할 문제라, 설계자와 확인할 목록에 올렸다.
5. 정리
커밋
meta-sc6 7968441 M7-5: sc6-bsp 비상정지(EM_INT)·누전(RCD_INT) 입력 -> K1 차단 + EVerest 오류 보고
오늘 배운 것
- 입력을 쓰기 전에 핀이 정말 입력인지 보자.
gpioinfo의input은 믿을 수 없다.pinconf-pins의 모드를 보자. - NC는 “아무것도 안 꽂음”도 이상이다. 시작하자마자 비상정지가 걸린 건 버그가 아니라 NC 설계가 의도대로 동작한 것이었다.
- 극성은 설정으로 빼 두자. 벤치에 버튼이 없어도 설정 한 줄로 시험할 수 있었고, 미검증인 누전 극성도 나중에 빌드 없이 바꿀 수 있다.
- 시험 순서가 꼬이면 로그를 실시간으로 보여 주자. 두 번은 “충전 중”이 아니라 “충전 전”에 비상정지를 걸었다.
K1 ON을 보고 나서 누르게 하자 한 번에 됐다. - “잘 동작한다”와 “맞게 동작한다”는 다르다. 자동 재개는 코드상 문제가 없지만, 제품으로서 맞는지는 요구사항이 정한다.
보드에 남아 있는 시험용 설정
| 위치 | 내용 |
|---|---|
/etc/default/sc6-bsp |
EM_ACTIVE=high (시험용, 실제 NC 버튼을 달면 low) |
다음 할 일
- 설계자 확인: 비상정지 해제 방식, 누전 시험 방법과 극성, 커넥터 종류(Type 1/2, 케이블/소켓), 최대 전류
- M7-6 (서비스, 자동 시작, 이미지 반영)은 위 결정이 나온 뒤 한 번에
댓글남기기