본문 바로가기

Software

아두이노 OBD 시뮬레이터 (Arduino OBD Simulator)

반응형

ELM327 블루투스 OBD 툴을 구매하여 사용하다보니 실제 OBD 데이터가 어떻게 요청하고 응답하는지 직접 보고 싶은 생각이 들게 되었습니다.

ELM327

OBD WiKi 문서를 참조하면 데이터 포맷을 유추 할 수 있습니다만

en.wikipedia.org/wiki/OBD-II_PIDs#CAN_(11-bit)_bus_format

 

OBD-II PIDs - Wikipedia

From Wikipedia, the free encyclopedia Jump to navigation Jump to search OBD-II PIDs (On-board diagnostics Parameter IDs) are codes used to request data from a vehicle, used as a diagnostic tool. SAE standard J1979 defines many OBD-II PIDs. All on-road vehi

en.wikipedia.org

제 경우는 직접 데이터 주고 받는것을 목격하기를 좋아하는바 

OBD 통신 학습시 도움이 될 만한 환경 구성을 해보고 공유하고자 합니다

 

보통 OBD 시뮬레이터 구매에는 가격이 부담이 있을 수 있습니다

알리에서 저렴한 제품으로 구매한다해도 60 달러가 넘어가는 가격에 시뮬레이터가 어떤 형태의 데이터를 주고 받는지 시뮬레이터측에서 보기는 어렵기도 합니다

 

해서 아두이노를 시뮬레이터로 활용하는 구성으로 온라인상의 유저분들의 노력을 통해 오픈되어 있는 자료들이 있습니다

그러한 자료 중 구성이 괜찮아 보이는 소스로 소개를 해드려 볼 까 합니다

 

일단 소스는 아래 사이트에 오픈이 되어 있습니다

github.com/spoonieau/OBD2-ECU-Simulator

 

spoonieau/OBD2-ECU-Simulator

Arduino Uno powered OBD2 ECU Simulator 'Stand alone or qt5 GUI control' - spoonieau/OBD2-ECU-Simulator

github.com

해당 소스의 사용을 위해서는 다음의 하드웨어들이 준비되어 있어야 합니다 (각 이름으로 검색하시면 찾아보실수 있습니다)

 

- 아두이노 우노 R3 호환 (4달러 정도)

- CAN-BUS 아두이노 쉴드 (4 달러 정도)

- ELM327 BLE 인터페이스 모듈 (알리에서 5~10 달러 선)

- OBD 포트 및 케이블 (알리에서 7 달러 정도)

- OBD포트 전원 공급용 12v DC 어댑터 (보통 집에 찾아보시면 굴러다니는 어댑터가... 있으면 좋겠습니다)

- 2.1mm x 5.5mm 어댑터 연결 소켓 (위 OBD 포트 전원선과 어탭터 연결용 / 관련 자작같은거 하신다면 몇 개 구비해놓으시는것 추천 드립니다)

 

위와 같이 준비후에 아두이노 위에 CAN-BUS 쉴드를 연결하고

OBD 포트의 CAN HIGH와 CAN LOW를 CAN-BUS 쉴드 상단에 명시된 CAN HIGH와 CAN LOW에 연결합니다

OBD 포트의 +12V와 GROUND는 12V 어댑터 + 와 - 에 각각 연결합니다 (GROUND 4, 5번을 함께 연결 합니다)

제 경우 아래와 같은 모양새가 되었습니다

여기까지 준비가 된다면 하드웨어 준비는 완료가 되었습니다

 

이제는 위에서 소개해드린 소스 OBD2-ECU-Simulator 사이트로 이동하여 아두이노 소스를 내려 받아 빌드합니다

아두이노 소스는 OBD2-ECU-Simulator/Open-Ecu-Sim-OBD2-FW 경로에 Open-Ecu-Sim-OBD2-FW.ino 이름으로 위치하고 있습니다 (참고로 해당 프로그램은 MCP_CAN Library https://github.com/coryjfowler/MCP_CAN_lib 을 사용합니다) 

 

아두이노 소스 빌드 및 아두이노에 업로드하는 과정은 여기서는 패스하도록 하고

아두이노에 프로그램이 업로드 완료되면 시리얼 모니터를 통해서 주고 받는 데이터를 확인 할 수 있습니다

 

부가적으로 OBD 통신은 데이터를 필요로하는 쪽이 요청하면 응답을 받는 형식이기에 요청을 위해서 ELM327 을 통한 스마트폰 앱을 통해 데이터 요청을 하도록 합니다

 

여기서 사용된 앱은 Car Scanner 앱을 사용합니다

앱에서 차종은 최근 현대나 기아차량 정도로 선택하신뒤에 ELM327 과 블루투스 연결 후 센서 데이터 보기를 하면 아래와 같이 요청된 데이터를 OBD 시뮬레이터에서 임의 값으로 응답하는 형태를 볼 수 있습니다

Car Scanner 앱 센서 데이터 보기

더불어 아두이노 시리얼 모니터를 살펴보면 시뮬레이터 프로그램에서 요청받고 응답하는 데이터를 모니터링 할 수 있습니다 (필요에 따라서는 아두이노 소스를 수정하여 원하는 데이터를 발생시키거나 응답 가능한 데이터를 추가해 볼 수도 있을것 입니다)

아두이노 시리얼 모니터 출력

참고로 원본 Open-Ecu-Sim-OBD2-FW.ino 소스의 경우 시리얼 출력시 10진수와 16진수가 혼용되어 출력되어 혼란의 소지가 있어 모두 16진수로 출력 될 수 있도록 수정을 하여 아래 수정된 diff 파일을 공유드려 봅니다 (참고로 아두이노에서 자동포맷 적용 후 수정되었습니다)

diff  -b C:/Open-Ecu-Sim-OBD2-FW.ino C:/Open-Ecu-Sim-OBD2-FW_Patch.ino
65a66
> String canMessageRead_Hex = "";
92a94,104
> void array_to_string(byte array[], unsigned int len, char buffer[])
> {
>     for (unsigned int i = 0; i < len; i++)
>     {
>         byte nib1 = (array[i] >> 4) & 0x0F;
>         byte nib2 = (array[i] >> 0) & 0x0F;
>         buffer[i*2+0] = nib1  < 0xA ? '0' + nib1  : 'A' + nib1  - 0xA;
>         buffer[i*2+1] = nib2  < 0xA ? '0' + nib2  : 'A' + nib2  - 0xA;
>     }
>     buffer[len*2] = '\0';
> }
230,235c242,250
<   byte engine_Coolant_Temperature_Msg[8] = {3, 65, 0x05, (byte)(engine_Coolant_Temperature + 40)};
<   byte engine_Rpm_Msg[8] = {4, 65, 0x0C, (byte)rpm_A, (byte)rpm_B};
<   byte vehicle_Speed_Msg[8] = {3, 65, 0x0D, (byte)(vehicle_Speed)};
<   byte timing_Advance_Msg[8] = {3, 65, 0x0E, (byte)((timing_Advance + 64) * 2)};
<   byte intake_Temp_Msg[8] = {3, 65, 0x0F, (byte)(intake_Temp + 40)};
<   byte maf_Air_Flow_Rate_Msg[8] = {4, 65, 0x10, (byte)maf_A, (byte)maf_B};
---
>   byte engine_Coolant_Temperature_Msg[8] = {0x03, 0x41, 0x05, (byte)(engine_Coolant_Temperature + 40)};
>   byte engine_Rpm_Msg[8]                 = {0x04, 0x41, 0x0C, (byte)rpm_A, (byte)rpm_B};
>   byte vehicle_Speed_Msg[8]              = {0x03, 0x41, 0x0D, (byte)(vehicle_Speed)};
>   byte timing_Advance_Msg[8]             = {0x03, 0x41, 0x0E, (byte)((timing_Advance + 64) * 2)};
>   byte intake_Temp_Msg[8]                = {0x03, 0x41, 0x0F, (byte)(intake_Temp + 40)};
>   byte maf_Air_Flow_Rate_Msg[8]          = {0x04, 0x41, 0x10, (byte)maf_A, (byte)maf_B};
> 
>   char str[64] = "";
>   int value = 0;
255a271,273
>       
>       //value = atoi(buf[i]);
>       canMessageRead_Hex = canMessageRead_Hex + String(buf[i], HEX) + ",";
257c275,276
<     Serial.println(canMessageRead);
---
>     canMessageRead_Hex.toUpperCase();
>     Serial.println(canMessageRead_Hex);
266c285,287
<       reply = String(String(REPLY_ID, HEX) + ",0,8," + String((char*)mode1Supported0x00PID));
---
>       array_to_string(mode1Supported0x00PID, 8, str);
>       reply = String(String(REPLY_ID, HEX) + ",0,8," + String(str));
>       reply.toUpperCase();
274c295,297
<       reply = String(String(REPLY_ID, HEX) + ",0,8," + String((char*)mode1Supported0x20PID));
---
>       array_to_string(mode1Supported0x20PID, 8, str);
>       reply = String(String(REPLY_ID, HEX) + ",0,8," + String(str));
>       reply.toUpperCase();
282c305,307
<       reply = String(String(REPLY_ID, HEX) + ",0,8," + String((char*)mode1Supported0x40PID));
---
>       array_to_string(mode1Supported0x40PID, 8, str);
>       reply = String(String(REPLY_ID, HEX) + ",0,8," + String(str));
>       reply.toUpperCase();
290c315,317
<       reply = String(String(REPLY_ID, HEX) + ",0,8," + String((char*)mode9Supported0x00PID));
---
>       array_to_string(mode9Supported0x00PID, 8, str);
>       reply = String(String(REPLY_ID, HEX) + ",0,8," + String(str));
>       reply.toUpperCase();
303c330,332
<       reply = String(String(REPLY_ID, HEX) + ",0,8," + String((char*)engine_Coolant_Temperature_Msg));
---
>       array_to_string(engine_Coolant_Temperature_Msg, 8, str);
>       reply = String(String(REPLY_ID, HEX) + ",0,8," + String(str));
>       reply.toUpperCase();
312c341,343
<       reply = String(String(REPLY_ID, HEX) + ",0,8," + String((char*)engine_Rpm_Msg));
---
>       array_to_string(engine_Rpm_Msg, 8, str);
>       reply = String(String(REPLY_ID, HEX) + ",0,8," + String(str));
>       reply.toUpperCase();
321c352,354
<       reply = String(String(REPLY_ID, HEX) + ",0,8," + String((char*)vehicle_Speed_Msg));
---
>       array_to_string(vehicle_Speed_Msg, 8, str);
>       reply = String(String(REPLY_ID, HEX) + ",0,8," + String(str));
>       reply.toUpperCase();
330c363,365
<       reply = String(String(REPLY_ID, HEX) + ",0,8," + String((char*)timing_Advance_Msg));
---
>       array_to_string(timing_Advance_Msg, 8, str);
>       reply = String(String(REPLY_ID, HEX) + ",0,8," + String(str));
>       reply.toUpperCase();
339c374,376
<       reply = String(String(REPLY_ID, HEX) + ",0,8," + String((char*)intake_Temp_Msg));
---
>       array_to_string(intake_Temp_Msg, 8, str);
>       reply = String(String(REPLY_ID, HEX) + ",0,8," + String(str));
>       reply.toUpperCase();
348c385,387
<       reply = String(String(REPLY_ID, HEX) + ",0,8," + String((char*)maf_Air_Flow_Rate_Msg));
---
>       array_to_string(maf_Air_Flow_Rate_Msg, 8, str);
>       reply = String(String(REPLY_ID, HEX) + ",0,8," + String(str));
>       reply.toUpperCase();
357c396,398
<       reply = String(String(REPLY_ID, HEX) + ",0,8," + String((char*)obd_Std_Msg));
---
>       array_to_string(obd_Std_Msg, 8, str);
>       reply = String(String(REPLY_ID, HEX) + ",0,8," + String(str));
>       reply.toUpperCase();
366c407,409
<       reply = String(String(REPLY_ID, HEX) + ",0,8," + String((char*)fuel_Type_Msg));
---
>       array_to_string(fuel_Type_Msg, 8, str);
>       reply = String(String(REPLY_ID, HEX) + ",0,8," + String(str));
>       reply.toUpperCase();
446a490
>     canMessageRead_Hex = "";
반응형