말리부 디젤 측후방감지기 DIY 번외편(CAN 스니핑)


1~4편에 걸쳐 말리부 디젤 측후방감지기 DIY기를 올리면서 4편 마지막에 과제(?)를 언급한 게 있다. ①센서 각도 재정렬 ②일정 속도 이상에서만 작동 이렇게 2개인데, 이번엔 ②에 집중해봤다.
순정 방식의 BSD는 일정 속도 이상에서만 작동한다. 실제로 써보니 왜 그런지 알 것 같았다. 대표적인 예가 우선 좌측 2개 차로가 좌회전 차선일 경우, 2차선에서 왼쪽 깜빡이를 넣고 대기하다가 신호가 떨어졌을 경우다. 이때 1차선 좌회전 차량이 나보다 더 빨리(!) 움직이면 알람이 울린다. 이외에도 골목길에서 도로로 합류할 때 차가 대각선 방향을 보고 있을 경우 바로 뒤에 오는 차도 감지해서 알람이 울리더라.
그냥 둬도 되지만, 최대한 순정에 가깝게 만들고 싶었다. 검색해보니 사제 BSD의 경우 GPS를 이용해 속도를 측정한 뒤, 일정 속도 이상일 때만 작동하게 하는 모듈이 판매되고 있었다. 대부분 판매 중지된 상태라 구하기도 힘들 뿐더러 가격도 4만원을 훌쩍 넘었다.
구하기 힘들면 GPS 방식의 속도 모듈을 자작하는 것도 어렵지 않다. 비용도 대략 1만원 가량이면 만들 수 있다. 하지만 GPS 수신까지 시간이 몇분 걸린다는 것이 문제다. (GPS 방식의 속도 모듈 판매 페이지에도 3~5분 걸린다고 나온다)
그래서 자동차 CAN에서 속도 부분을 찾아 내 알람을 동작하는 모듈을 만들기로 했다. 아래는 ESP32 Super Mini와 SN65HVD230로 구성된 간단한 테스트용 회로다.

SN65HVD230는 CAN Transceiver라는 놈으로 자동차의 CAN 신호를 받아 ESP32에 전달하는 역할을 한단다.

예전에 ELM327을 이용해 자동차 OBD2 단자에서 데이터를 읽어와 DPF 상태를 보여주는 DIY를 해봤기 때문에 이번에도 쉽게 할 수 있을 것이라 생각했다. 하지만 지난번엔 질의-응답 방식이었고, 이번엔 1초에도 수십번씩 나오는 자동차 데이터에서 속도 부분을 받아서 처리하는 식이라 접근 방법이 완전히 달랐다. (이런 방식을 CAN 스니핑이라고 한다나 뭐라나)
잠깐 두 방식의 차이에 대해 자세히 설명하면, 기존 질의 응답 방식은 ①ELM327을 통해 자동차에 어떤 정보를 원하는지 물어본다 ②자동차 ECU가 질의에 대해 답변을 한다 ③받은 정보를 적당한 형태로 가공해 디스플레이에 표시한다. 이런식으로 진행된다. 그런데 DPF 포집량 처럼 변화가 많지 않은 항목은 그저 5초에 한 번씩 질의하고 응답 받아도 큰 문제가 없다. 하지만 이번에 구상한 속도 감응형 모듈은 데이터 값의 변화가 빠르다. 그래서 질의-응답의 간격을 줄여야 하는데, 이게 자동차 ECU에 부담이 될 수도 있다고 한다. 그렇다고 간격을 5초쯤으로 하자니, 반응이 더딜 수 밖에 없다. 그래서 질의-응답 방식이 아니라 자동차가 일방적으로 뿌리는 메시지에서 속도 부분을 받아서 처리하는 방식이 낫다는 결론이 나왔다.
문제는 질의-응답 방식은 속도, RPM 등 흔히 필요한 데이터는 표준화가 돼 있고, 제조사 및 차량 특유 데이터라도 인터넷에 관련 정보가 많이 공개돼 있지만, CAN 스니핑 방식은 데이터가 별로 없다는 것이다. 제조사마다 차종마다 다 달라서 일일이 CAN 데이터를 보면서 속도 변화와 유사한 데이터를 찾아 삽질을 해야 한다는 문제가 있다.
이런 고민을 ChatGPT와 나누면서(?) 해결에 들어갔다. 우선 자동차로부터 나오는 모든 데이터를 시리얼 모니터로 출력할 수 있는 아두이노 코드를 요구했다
######
include <driver/twai.h>
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("ESP32-C3 Starting FULL CAN Dump...");
// TWAI (CAN) 기본 설정
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_7, GPIO_NUM_8, TWAI_MODE_NORMAL);
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS(); // 차량용 기본 500kbps
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL(); // 수신 필터링 없이 전부 받기
if (twai_driver_install(&g_config, &t_config, &f_config) == ESP_OK) {
Serial.println("TWAI Driver installed successfully.");
} else {
Serial.println("TWAI Driver install FAILED.");
while (true); // 실패시 멈춤
}
if (twai_start() == ESP_OK) {
Serial.println("TWAI Driver started successfully.");
} else {
Serial.println("TWAI Driver start FAILED.");
while (true); // 실패시 멈춤
}
}
void loop() {
twai_message_t rx_frame;
// 10ms 안에 수신된 CAN 프레임 읽기
if (twai_receive(&rx_frame, pdMS_TO_TICKS(10)) == ESP_OK) {
Serial.print("ID: 0x");
Serial.print(rx_frame.identifier, HEX);
Serial.print(" DLC: ");
Serial.print(rx_frame.data_length_code);
Serial.print(" Data: ");
for (int i = 0; i < rx_frame.data_length_code; i++) {
if (rx_frame.data[i] < 0x10) Serial.print("0"); // 예쁘게 출력
Serial.print(rx_frame.data[i], HEX);
Serial.print(" ");
}
Serial.println();
}
}
####
이상이 ChatGPT가 짜준 코드다. 이걸 ESP32에 업로드하고 자동차 OBD단자의 CAN High와 Low에 연결해 준 뒤 스마트폰에서 시리얼 출력 로그를 저장했다. 그리고 GPT에게 던져주면서 어떤 ID 값이 속도일지 물어봤다.
GPT는 나름대로 몇 가지 후보를 줬고, 그 ID 값만 수신하도록 다시 코드를 짜서 업로드 후 테스트했다. 하지만 속도계에서 보이는 값과 시리얼 모니터에 나오는 값은 그다지 관계가 없어보였다. (기어를 N에 두고 엑셀러레이터를 밟아서 rpm만 띄워도 값이 변하는 등의 증상) 이 작업을 거짓말 조금 보태 수십번했다. 하지만 GPT가 주는 후보 ID 중에선 속도 값을 찾을 수가 없었다.
결국 방향을 선회, CAN 데이터를 전문적으로 분석하는 오픈소스 툴 SavvyCAN을 이용해보기로 했다. 자동차가 주는 CAN 데이터를 풀로 캡쳐해서 올리면 ID와 Byte별로 나눠서 데이터 값의 변화를 그래프로 그려주는 기능이 있더라.
이걸로 모든 CAN ID를 일일이 돌려보며 후보군을 몇 개 찾았다. 그리고 그 ID만 잡아서 시리얼 모니터에 출력해주는 코드를 짜서 테스트해봤지만 결국 실패했다. 여기까지가 한 일주일 짬짬이 삽질한 결과다.
이 방식을 계속 이어가는 것은 시간도, 능력도 부족했다. 결국 전 세계 어딘가에서 누군가는 이와 비슷한 짓(?)을 해보고 그 결과를 남겼을 것이라고 생각하고 검색을 좀 더 정밀하게 해보는 것으로 방향을 틀었다.
검색하다보니 https://docs.google.com/spreadsheets/u/0/d/1qEwOXSr3bWoc2VUhpuIam236OOZxPc2hxpLUsV0xkn0/htmlview# 이런게 걸렸다. 이게 무슨 뜻인지 몰라서, GPT에게 링크를 던져주며 여기서 힌트가 될만한 게 있을지 물었다.
0x0C9
메시지는 일반적으로 "General Engine Status 1"로 알려져 있으며, 초기에는 6바이트 길이였으나 이후 8바이트로 확장되었습니다. 이러한 변화는 특정 바이트의 의미가 차량 모델이나 연식에 따라 다를 수 있음을 의미합니다.pcmhacking.net
차량 속도 정보를 찾기 위해서는 다른 CAN ID를 분석하는 것이 좋습니다. 예를 들어, 0x3E9
ID의 첫 번째와 두 번째 바이트를 결합하여 16진수로 해석한 후, 이를 0.01로 곱하면 mph 단위의 속도를 얻을 수 있으며, 이를 1.609로 곱하면 km/h로 변환할 수 있습니다. pcmhacking.net
그랬더니 GPT가 이런 답변을 내놓는 것이 아닌가. 두 번째 문단이 힌트가 됐다. 원문을 찾아가보니, I might be able to help. But this can be very complicated. Also, my examples come from a 2009 truck. 이라는 단서를 달려 있었다.
하지만 GPT가 찾아준 대로 Vehicle speed is in 0x3E9. You want the first and second bytes. Convert from hex to decimal, multiply by 0.01 to get mph. You can multiply mph by 1.609 to get kph.
0x3E9 03 40 0A 95 03 00 40 00 - 0340 = 832. 832 * 0.01 = 8.32 mph. 8.32 * 1.609 = 13.39 kph
0x3E9 0B 0B 11 5B 0A 80 40 00 - 0B0B = 2827. 2827 * 0.01 = 28.27 mph. 28.27 * 1.609 = 45.49 kph
이런 쓰레드가 있었다. 내차는 2014년식인데다 디젤(말리부 디젤은 한국에서만 출시됐다)이라 맞을지 확신은 없었다. 하지만 자동차 회사가 연식 변경에 따라 CAN ID를 일부러 바꾸진 않았을 것이란 생각이 들었다.
GPT한테 바로 0X3E9의 첫 번째 바이트와 두 번째 바이트를 결합해 16진수를 10진수로 바꾼 뒤 0.01(mph 단위)를 곱하고, 다시 1.609(mph-->km/h 변환)를 곱한 값을 시리얼 모니터에 출력하는 코드를 짜달라고 했다.

그 결과... 자동차의 속도 값을 찾을 수 있었다!!! (실제로 속도계에 표시된 것보다 2~3km/h 정도는 낮게 나오던데, 이것은 안전운전(?)을 위해 실제 속도보다 계기판에 조금 높게 표시해주기 때문으로 보인다) 기어 N단에서 rpm만 올려도 시리얼 모니터엔 0으로 표시됐고, 후진 시에도 속도 데이터가 올라왔다.
나 같은 취미 코더 +DIYer에겐 GPT가 신세계를 열어줬다. 이번에만해도 GPT가 모든 것을 다 해주진 않았지만, ESP에 올릴 코드를 금방 대신 짜주고, 결정적인 힌트도 찾아줬다.
이제 남은 것은 ESP32에서 속도 값을 받아 30km/h(혹은 40km/h) 이상일 때만 BSD를 작동시키도록 회로를 구성하는 것이다. 아예 BSD 전체의 전원을 이걸로 제어할까도 고민했지만, 그것보다는 알람 스피커만 제어하는 것이 시스템 안정성을 위해 나을 것 같다.
딸깍거리는 소리가 듣기 싫어 일반적인 릴레이 말고 Solid State Relay를 주문했다. 막상 받아보니 얘도 부피가 은근히 크더라. 딱 미니 빵판에 들어갈 정도로 구성하려고 GPT에게 물어봤더니 AQY212G라는 걸 추천해주더라. 데이터시트를 찾아보니 릴레이와 거의 비슷하게 동작한다. (뭔 포토커플러 어쩌고 설명이 있던데...자세한건 모르겠음)
바로 알리에 AQY212G를 주문해뒀다. 한 일주일 뒤면 도착할테니 최종 버전을 만들면 다시 업데이트 하겠다.