|
OBD2 포트를 이용한 실시간 바이크 상태/정보 얻기
OBD2 포트를 이용한 실시간 바이크 상태/정보를 저렴(?)하게 읽어 보려고
이것 저것 테스트도 하고
적당한 기성품이 있으면 사려고 했는데
가격이 너무 비쌉니다.
자작을 할 수 있을까 알아 보다가 아래와 같이 주저리주저리 작업을 해 왔습니다.
제작에 들어 가려고 하는데
바쁜 연말에 걸린데다가 송년회에 다니다 보니 늦어집니다.
그러던 와중
엊그제 '인도'사이트에서 염가(?)에 스마트폰용 스캐너 패키지를 판매한다고 나와 있네요. 헐...
3500루피라고 하니 대략 한국 돈으로 6만원...
유료 앱과 연동케이블 가격입니다.
ELM327은 제외...
그 가격이면 조금 욕이 나오지만...
힘 빠지네요...
Harley 전 모델을 지원하는 것은 아니라서 조금 더 알아 보겠습니다만...
그동안 수집하느라 수고했던 작업 정보들이 아까워 회원 여러분들과 공유합니다.
전기자동차를 비롯 앞으로 자동차 전장 산업이 크게 뜰 것으로 사료됩니다. 알아 두면 좋을 것 같습니다. 도움이 되시길....
================================
OBD세상 들어가기
================================
최근 IT 기술이 발전하면서 IoT 기술에 대한 관심이 커지고 있습니다.
2000년도 이후 출시된 자동차에는 거의 모두 OBD2 포트가 달려 있어서 조금만 관심을 가지면 누구나 차량의 상태를 모두 읽어 볼 수 있습니다.
제 차에 사용 중인 카 모니터링 화면입니다.
RPM, 속도, 전압, 평균연비, 엔진온도, 인테이크 온도, 고도, 수온, 이산화탄소 발생량 등등 ... 물론 에러코드도 읽어 냅니다.
이 스캐닝 방법은 단지 ELM327/OBD2라는 조그만 전자장치(지우개 크기)를 사서 장착하고
공짜 스마트폰 앱으로 읽어 보는 것으로 가능합니다.
(OBD2)
OBD II (ON BOARD DIAGNOSTICS 2)는 인터넷장터 어디서나 쉽게 2만원 정도에 구입 가능합니다. 중국제가 더 많을 듯....
스마트폰 앱 역시 Torque, Caroo 등 무수히 많은 공짜 앱이 있습니다.
(Apps)
=======================================
OBD-2 스캐너의 개요
=======================================
운전자가 차량을 운행할 때, OBD 장치는 지속적으로 차량의 상태를 감시하게 되어 있습니다.
엔진 점화 계통이나, 배기장치 등에서 발생하는 문제 등을 감지하면, 문제를 상징하는 MIL 램프를 켜게 되고, 문제가 해결되기 전 까지는 꺼지지 않게 되어 있습니다.
차량 제조사에 따라서 또는 지역에 따라서 OBD 의 원래 목적인 엔진 계통의 이상으로 인한 필요 이상의 배기가스 발생에 대한 자체 검사 기능뿐만 아니라, 추가적으로 차량 내부의 다른 시스템들의 이상을 표시해 주기도 합니다.
아래의 그림이 MIL 을 의미하는 아이콘입니다.
이 램프가 대시보드에 표시되면 OBD 장치가 차량의 이상을 감지 했음을 의미합니다.
이 때는 운전자가 정비사에게 진단을 받아서 해결 하라는 의미이며, 정비사는 OBD 장치의 세부 진단 결과를 읽어 내는 장비를 통해서 문제를 분석, 해결하게 됩니다.
원리는
1. 자동차에 장착된 모든 센서의 정보를 ECU가 받아 CAN(Car Area Network) 등을 통해 OBD포트로 정보를 제공합니다.
2. OBD2 장치는 이 데이터를 블루투스나 WiFi, 또는 유선으로 단말기에 정보를 전달합니다.
3. 스마트폰이나 단말기에서 데이터를 읽어 화면에 적절히 보여 줍니다.
차량의 OBD 포트는 운전석 핸들 하단에 보통 달려 있습니다. 10여년 전의 차면 아니면 어느 차나 다 달려 있습니다.
다음의 항목들은 대략적인 OBD의 역사입니다.
o 1982 년
- 미국 General Motors 사가 ALCL(나중에 ALDL로 변경)라고 불리는 내부 표준을 정하고, 판매용 차량에 탑재함.
o 1986 년
- ALDL 의 개선된 버전이 나옵니다. 8192 Baud 속도와, 반 이중 방식(Half duplex)의
UART 신호를 사용.
o 1987 년
- LA 지역의 대기 문제에 대응하기 위해서, 캘리포니아 대기 자원국 (CARB)에서 1988 년부터 생산되고, 캘리포니아에서 판매되는 모든 차량에 대해서 기본적인 OBD 기능을 포함하도록 요구. 이 요구사항에 대한 부분을 나중에 OBD-I 표준이라고 부르게 되지만, 아직 커넥터의 형태나, 데이터 프로토콜에 대해서도 정해진 표준은 없었음.
o 1988 년
- 미국 자동차 공학회 (SAE) 에서 자체진단을 위한 커넥터와 신호형식들에 대해서 표준안을 제시.
o ~1994 년
- 미국 전역에 대해서 배기가스에 대한 테스트 필요성이 대두 됨에 따라서 캘리포니아 대기 자원국 (CARB) 가 OBD-II 명세서를 발표함.
- 캘리포니아에서 판매되는 모든 1996 년 이후 생산 차량에 대해서 OBD-II 를 적용하도록 규정.
o 1996 년
- 미국에서 판매되는 모든 차량에 대해서 OBD-II 를 지원하도록 법제화.
o 2001 년
- 유럽연합이 OBD-II 를 변형한 형태인 EOBD 를 지정. 2001 년 이후 생산되어서 유럽에서 판매되는 모든 가솔린 차량에 대해서 사용하도록 법제화.
o 2008 년
- 미국에서 판매되는 모든 차량에 대해서 ISO15765-4 신호 표준을 지원하도록 할 예정.
미국과 한국의 4륜 차량은 이미 법제화되어 지원합니다만 이륜차는 권고사항일 뿐입니다.
제가 장황하게 차량용 OBD를 소개한 이유는
차량 뿐만 아니라
오토바이에도 이 장치를 사용할 수 있게 하면 좋겠다는 생각이 들어서입니다.
투어에 나가면
화려(?)하게도 많은 오토바이에 전압계, 타코메타, 속도계 등등이 주렁주렁 달려 있습니다.
보기에는 화려해 보이지만 좁은 핸들 위에 정신없지 않습니까?
사실 이 정보만 잘 처리하면 스마트폰 하나로 모두 읽을 수 있게 되겠죠.
이 정도 바이크의 정보를 실시간으로 읽을 수 있으면 좋지 않겠습니까?
아래 여러 개의 그림을 보시면
기어는 3단 또는 뉴트럴(중립)에 잡혀 있고, 속도는 0, 전압은 12.8V 이네요.
그 아래 그림은 더 상세한 정보입니다.
앞 실린더, 뒷 실린더 정보도 있습니다. 기어는 N, 중립입니다.
맨 아래쪽 그림은 다른 기종인
BMW의 정보인데 ... 이산화탄소 발생량, O2센서의 상태도 다 나옵니다.
이륜차와는 이렇게 연결됩니다.
자료를 좀 찾아 보니
차량은 이미 10여년 전부터 국제표준을 지원하기 시작했는데
불행히도 오토바이는 할리, BMW, 두카티 등에서 가능한데 그것도 최신 모델에서만 지원되고 국산 오토바이는 아직 지원이 안되나 봅니다.
물론 국제표준인 OBD가 아닌 자체 인터페이스를 이용하여 검진을 합니다.
KR 오토바이수리점에서 다이그노시스 스캐너 디바이스(진단장치)로 에러코드나 장애를 찾아내는 것을 보셨을 겁니다.
하지만 장비 가격이 비싸고 (대략 40-50만원대), 기변시 연결 커넥터를 별도 구입해야 하며, 국제표준을 지원하지 않는다는 문제점이 있습니다.
일부 장비는 와이파이나 블루투스 통신으로 스마트폰에 직접 연동되는 앱도 있습니다. 미라쥬를 만든 효성도 이를 지원하는 것으로 알고 있습니다.
ECU를 만든 외국업체에서 제공해 줄 수도 있고요.
성남의 상아 사장님도 가끔씩 꺼내서 찍어 보더군요. ㅎㅎㅎ
(KR에서 조만간 국제표준을 지원하는 진단포트, 앱이 나오겠지요. 이미 개발해 두고 공장 내부에서만 쓰는 지도 모르겠군요. 아직 준비 중이라면 재능있는 미라주 회원들의 재능기부도 해서 좀 더 빨리 출시되었으면 합니다)
(범용 스캐너 장비)
(creader 7&5) (launch x431/idiag)
** http://cafe.naver.com/autowave21/326292
자 그럼 본격적으로 작업에 들어 가 봅시다.
준비물은...
- ELM327(블루투스)
- 스마트폰 (Torque 설치 및 설정 .... 블루투스장치 찾기, 페어링하기, 앱에서 입력디바이스를 블루투스로... 실린더크기 등등은 대충 입력)
- 인터페이스 케이블 .... 오토바이에서 제공하는 OBD 포트 모양이 모두 다름. 표준은 16 포트male 인데 둥근형(BMW), 사각형(할리)... 제각각입니다.....
등 입니다.
======================================
오토바이 OBD2 데이터 읽기
======================================
-------------------
1. 할리
-------------------
o 테스트 기종
- Harley Davison FXDC / 2013 - Dyna (Super Glide Custom)
- 일단 OBD포트가 있는지 확인해 봅니다.
인터넷을 뒤져보니 좌석 우측 ECU 커버 안에 있네요.
그런데 포트가 표준 규격이 아닙니다. 헐...
AMP에서 나온 Deutsch 6 pin 수놈이네요...
그나마 2012년까지는 4 pin형입니다.
o 케이블 제작
4 pin Deutsch 소켓과 OBD2 female 연동케이블은 인터넷에 팝니다. 가격이 2만원?
6핀은 더 비쌉니다. AlienTech에서 39유로? 약 5만원... 미친 넘들.. 케이블 하나에 무슨 5만원씩이나 (배송료 제외...)
얼른 이베이/알리익스프레스에 가서 OBD2 female, 6-pin Deutsch female 제작용 소켓을 단품으로 구입합니다.
핀에 선을 clamping 하여 하나 하나 작업합니다.
200개씩 사면 500원도 안할 소켓을 4-5천원씩 주고 샀습니다.
(참고로 소켓작업을 한 케이블은 2-3만원씩 하네요... )
오호라...
OBD가 20~30년 동안 발전해 오면서 지원하는 프로토콜이 여러 종류네요...
ISO가 있고, J1850이 있으며 CAN도 있습니다.
좀 더 구체적인 용어를 쓰면 VPW-PWM (SAE-J1850), CAN(ISO 15765, SAE-J2234), ISO (ISO 1941-2, ISO 14230-4) 이고,
전송 신호 방식은 자체는 PWM이나 UART, GPIO 통신과 비슷한 면이 많은 듯 합니다.
CAN 은 보쉬와 인텔이 공동 개발하여 유럽쪽 차종 대부분에 사용을 하고 북미쪽에서 사용하는 J1850은 SAE에서 사용하는 자동차배선 표준입니다. 이두가지는 서로 전혀 다른 방식이고 서로 호환되지 않습니다.
국내에서 생산되었고, 생산될 모든 차량은 CAN을 사용합니다.
CAN통신만 되는 스캐너는 한국, 일본, 크라이슬러 외 미국 일부 차종,유럽 차량 대부분만 통신이 되겠죠.
포드, 홀덴, 폰티악, GMC, 마쓰다 일부차량, 폭스바겐 일부, 닷지, 시보레, 뷰익, 크라이슬러 일부 차량은 J1850이 지원이 되어야 합니다.
인터넷을 뒤져 보니
2012년까지 J1850, 2013년부터 일부 기종은 CAN을 쓴다고 나와 있습니다. 헐...
할리 엔지니어도 이것 저것 다 적용해 본 것 같습니다.
일단...
CAN으로 생각하고 작업합니다.
(안되면 다른 프로토콜도 적용해 봐야 하므로 핀은 모두 작업해 둡니다.)
할리에서 제공하는 6 pin deutsch(암)의 핀 할당은 다음과 같습니다.
1 W/R ..... CAN+
2 BK ........ GN D
3 W/BK ... CAN-
4 BE/R ..... 브레이크등
5 R/Y ...... ACC ............... 키온시 전원
6 R/Be ..... 휴즈 block ..... 상시전원
따라서 연결은...
Odb2(암) 6 pin deutsch(암)
4,5. <--------> 2. Ground
6. <----------> 1. Can+
14. <---------> 3. Can-
16. <---------> 5. Power
“6pin Deutsch female – 20pin OBD2 female“ 케이블이 완성되었습니다.
명칭이 길어서 여기서는 그냥 "obd연동케이블"로 정의하겠습니다.
참고로 4 pin Deutsch socket을 지원하는 할리 모델과 포트 할당은 다음과 같습니다.
Compatible with 4-PIN Harley Davidson Bikes!
2001-2010 HD Softail Models
2004-2011 HD Dyna Models
2007-2013 HD Sportster 883/1200 Models
2002-2013 HD V-Rod Models
2002-2013 HD Touring Models
Harley data port 16 pin ELM327
---------------------------------------------------------
(x) 1
(brown) 2 ------------- ground ------- 4 and 5 (ground)
(green) 3 ------------- data ---------- 2 (J1850+)
(white) 4 ------------- +12V ---------- 16 (+12V)
+++ 주의할 점 : Batt 상시전원에 연결되기 때문에 ELM327을 계속 설치해 놓을 경우 방전될 우려가 있습니다. 4번핀의 경우는 따로 전원차단장치를 설치하지 않으면 답이 없는데.... 6핀을 사용할 경우는 가능한한 6번보다 ACC인 5번핀을 사용하는 것이 좋을 것 같습니다.
아파트 지하 2층 주차장으로 내려 갑니다.
할리 시트를 내리고 오른쪽 ECU 커버를 벗깁니다.
조금 지저분하지만... 오른쪽 회색 케이스가 OBD2 포트입니다.
방수 커버를 벗기니 6 pin의 소켓이 나오네요.
먼저 CAN 통신으로 테스트를 해 봅니다.
OBD2연동케이블을 할리 OBD(male) 포트와 ELM327(male)을 꽂습니다.
배터리전원을 쓰므로 그냥 꽂기만 해도 ELM327 LED에 빨간 불이 들어오네요.
- 일단 ELM327을 먼저 파워 온하고,
- 스마트폰 설정을 합니다.
설정-블루투스... 기기검색... ELM327선택-페어링
토크 앱을 수행시킵니다. 메뉴가 나타나면 설정으로 들어가서 연동을 블루투스, 기기를 ELM327로 선택합니다.
스마트폰과 ELM327이 페어링을 마치면 ELM327의 파란색 LED가 깜빡입니다. 블루투스 통신이 되고 있다는 의미입니다.
이제 초기 작업이 모두 끝났네요...
오토바이 시동을 겁니다. (시동이 켜져야 ECU가 동작하니까요)
헐...
답이 없습니다.
아무 것도 읽어 낼 수가 없네요...
(자동차의 경우 시동걸고 2-3초 지나면 RPM과 전압 등등이 하나씩 나타나는데....)
10초를 기다려봐도 감감 무소식입니다.
배선을 잘 못했는지 확인해 봅니다.
6/14번(CAN) 핀에 잘 연결되어 있습니다.
설치 상 문제는 없습니다.
자동차든 이륜차든 RPM과 전압 등은 최소한 읽을 줄 알았습니다. (순진하긴....)
일단...
인터넷 정보와 달리 CAN 대신 ISO나 J1850 프로토콜을 쓸 수도 있겠다 싶어 각각 프로토콜을 다시 실험해 봅니다.
2/10번, 7/15번을 연결해서 테스트해 봅니다.
(각 프로토콜 별 포트 전압이 걱정이 조금 됩니다. 5, 9, 12V를 쓸텐데... 괜시리 5V 포트에 12V 걸었다가 할리 ECU를 태워 먹을 수도 있겠다는 생각이 드네요... )
테스트 결과 각각 모두 무응답입니다.
다시 집으로 복귀...
인터넷을 다시 꼼꼼히 뒤져 봅니다.
몇군데 사이트에서 DYNA/2013은 CAN 통신을 쓴다고 적혀 있습니다.
자신감을 갖고 다시 지하주차장으로 내려 갑니다.
연동선을 연결하고 시동을 다시 겁니다.
여전히 응답은 없는데...
자세히 보니
토크 앱의 상단에 뭔가가 디스플레이되네요...
저장할 수 없어 기억에 의존하다보니 정확하진 않는데... 이런 내용이었습니다.
[00:08.620] Opening OBD-II Connection...
[00:08.716] Searching for ELM interface...
[00:08.725] Checking Bluetooth...
[00:08.824] Found ELM interface on Bluetooth!
시동 켬....
[00:38.718] Searching ECU..
[00:39.122] ECU Connection Completed...
[00:39.527] Reading VIN...
[00:40.161] Warning: Not scanning unsupported parameter:
내용으로 봐선 블루투스 연동이 되었고...
시동을 거니 ECU가 동작되어 토크 앱과 연동이 되었네요...
그런데 제조사 별로 데이터 포맷이 다른가 봅니다. 전혀 정보를 받아 내지 못하네요.
한넘은 한국어로 하고 할리는 영어로 하는가 봅니다.
못알아 먹고 있습니다.
(알고보니 제조업체별로 PID가 다르다고 합니다. PID는 위키피디아에 잘 나와 있습니다. 아래 사이트 참고)
OBD-II PIDs
https://en.wikipedia.org/wiki/OBD-II_PIDs
ISO나 1850은 어떤지 핀을 바꿔가며 다시 테스트 해 봅니다.
엥... 이쪽은 전혀 아니네요....
ECU 조차 찾지 못합니다.
확실히 다이나는 CAN통신을 하나 봅니다.
여기까지....
(테스트를 여기까지 하던 중 인터넷에서 값이 비교적 싸 보이는 기성품을 발견했습니다. 이 앱을 한번 써보겠습니다.
내용을 보시려면... 아래 ... OBD2 구매해서 테스트/기성제품 테스트로 Jump)
-------------------
2. BMW
---------------
o 테스트 기종 및 장치
- 보유한 BMW가 없는 고로 인터넷 정보에 의존합니다.
o 확인
- OBD 2 포트가 있습니다. 포트 모양이 다르네요. 동그랗게... 20핀입니다.
- 포트 할당은 다음과 같습니다.
- 한 친구의 경험담을 그대로 옮깁니다. 대충 이 친구의 말로 아이폰 앱으로 정보를 읽을 수 있었다는 것 같군요.
. PLX Wiki (OBD2 wifi dongle)을 갖고 있으며 아이폰에 DashCommand 앱으로 성공적으로 사용했다. 내 BMW는 CAN버스를 쓰므로 6개의 핀으로 obd2와 연결하면 동작할 것으로 생각했다.
www.hdforums.com/forum/softail-models/769557-2011-2012-softail-data-port-wiring.html
o 기성품 판매
- 기종별 패키지로 파는군요. 몇가지가 있는데 가격은 조금 비쌉니다. $300~$400... OBD2 장치와 스마트폰앱 또는 전용단말기입니다.
참고...
http://www.f650gs.crossroadz.com.au/Diagnostics.html
- 유용한 사이트
DIY OBD2 adaptor for BMW S1000RR and Ultragauge Install : https://www.youtube.com/watch?v=Igj4vt3UXBU
========================================
OBD-2 스캐너 구매해서 테스트
========================================
제작에 들어 가려고 인터넷 사이트를 여기 저기 뒤지는데...
헐... 인도 사이트에서 염가(?)에 스마트폰 앱 패키지를 판매한다고 나와 있네요. 헐...
예전 2010년도 초 독일 벤처업체에서
이와 동일한 패키지를 팔았는데...
올해 찾아 보니 없었습니다...
화면 디지인이 똑 같은데...
아마 인도 업체에 팔거나... 아니면 독일업체의 엔지니어가 인도사람이었고... 그 친구가 인도로 귀국해서 회사를 차린 건 아닌지...
아뭏튼
무료 앱이 있고 유료 앱이 있고....
유료 앱과 연동케이블 가격으로 3500루피라고 하니 대략 한국돈으로 6만원...
ELM327 제외...
무슨 넘의 케이블 제작에 6만원인감? 욕 나오지만... 그나마 무료앱이 있다고 하니...
진짜인가 싶어서 조금전
Play스토어에 들어가보니 Stelian Pop이라는 회사의 무료 HarleyDroid 앱이 있네요.
J1850만 지원합니다...
조금전 업체 담당과 메일을 주고 받았는데...
지금은 할리 뉴 모델용, CAN 통신도 개발되었으며 판매 가능하다고 합니다.
할리 Dyna 2013은 CAN을 쓴다고 까지 알려 주네요. ㅎㅎㅎ
호오라... 그렇다면 특별히 케이블을 안사도 무료 앱은 조만간 구할 수 있을 듯 싶습니다.
인도 영업사원이 잔머리 굴려 앱을 안주면 할 수 없지만...
그렇다면 ... 무료앱을 얻을 수 있다면 굳이 살 필요가 없겠네요.
케이블은 이미 만들어 두었고 ....
테스트를 또 해 봐야지요...
날씨도 추워졌는데 이번 주말 또 지하주차장에서 쭈그리고 앉아야겠네요.
기대하시라....
(추가내용입니다....
이틀 후 토요일 오전, 지하 주차장에 가서 테스트해 봅니다.
구글 플레이 스토어에서 무료앱을 설치했습니다.
케이블은 자작했고.. 이 무료앱이 연동만 된다면 따로 안사도 될 것 같다고 생각했죠..
OBD포트에 연결하여 오토바이 시동을 거니...
블루투스 연동이 됩니다.
그런데... RPM 등 데이터는 안 읽히네요...
뭐가 잘못되었나?
셋업, 설정 등등을 모두 확인해 봅니다.
별 문제없어 보이는데.. 데이터를 읽지 못하네요...
여기까지.. 실패담....입니다.)
=======================================
OBD-2 스캐너의 제작
=======================================
방법 1: 상용제품인 ELM327은 그대로 쓰고 스마트폰 용 안드로이드 앱을 개발
방법 2: 아두이노 개발 ... 아두이노 공개소스를 구해서 개발
- 인터넷 상에 아듀이노 ODB-II 스캐너 제작 사이트가 있고 프로그램 소스와 ECU 에뮬레이터 실행 프로그램이 제시되어 있음. (CAN 방식이 아닌 ISO14230 프로토콜을 적용한 예제입니다. 소스파일은 obduino2.pde 파일 포맷으로 되어 있네요)
[ref]
아두이노 스캐너 제작 (ISO)
http://www.avrtools.co.kr/technote7/board.php?board=download&command=body&no=95
아듀이노 OBD-II PID 송수신 함수
http://www.avrtools.co.kr/technote7/board.php?board=download&command=body&no=97
아듀이노 OBD-II PID 처리함수
http://www.avrtools.co.kr/technote7/board.php?board=download&command=body&no=99
-----------
프로토콜 분석
------------
Harley J1850 protocol:
The J1850 stream contains the following commands (found by trial and error, no official Harley Davidson documentation was used):
28 1b 10 02 xx xx : rpm, xxxx = rotations/minute * 4
48 29 10 02 xx xx : speed, xxxx = km/h * 128
48 3b 40 xx : gear going in neutral if (xx == 0xA0), going off neutral if (xx == 0x20)
: clutch engaged if (xx & 0x80)
48 da 40 39 xx : turn signals, xx = 1,2,3 for left/right/both
68 88 10 03 : check engine indicator off
68 88 10 83 : check engine indicator on
a8 3b 10 03 xx : current gear, xx = 1,3,7,15,31,63 for gears 1-6
a8 49 10 10 xx : engine temperature, xx = degrees Celsius + 40
a8 69 10 06 xx xx : odometer, xxxx = ticks, each tick = 0.4 meters
a8 69 10 86 xx xx : same as above, but a wraparound occured
a8 83 10 0a xx xx : fuel consumption, xxxx = ticks, each tick = 0.00005 liters
a8 83 10 8a xx xx : same as above, but a wraparound occured
a8 83 61 yy xx : fuel gauge, x = level (0-15)
: low fuel light on if (yy & 0x80), off if not.
----------
소스코드
----------
인터넷에서 구한 소스코드입니다.
C 언어로 되어 있고...
it works ! .... 동작하는 거라고 하네요.
프로그램이 무지 긴데... 일부만 게재합니다.
자세한 것은 아래 사이트를 방문하시면 될 것 같습니다.
http://custombaggerforum.com/forum/archive/index.php?t-551.html
//////////////////////////////////////////////////////////////////////////////
//
// Harley Davidson Handlebar Control Interface
//
// Written by: Len Shelton
// License: Attribution-ShareAlike 2.0
// http://creativecommons.org/licenses/by-sa/2.0/legalcode
//
//////////////////////////////////////////////////////////////////////////////
#define outputPin 13
#define in1 2 // left common
#define in2 3 // +
#define in3 4 // audio
#define in4 5 // -
#define in5 6 // dn
#define in6 7 // sel
#define in7 8 // right common
unsigned int VOL_UP[49] = {0,1,0,0,0,1,0,1,0,1,0,0,1,0,0,1,0,1,0,0,0,0,1,0,0 ,0,0,1,0,0,1,0,0,0,0,1,0,1,0,0,1,0,0,1,0,1,0,1,0};
unsigned int VOL_DN[49] = {0,1,0,0,0,1,0,1,0,1,0,0,1,0,0,1,0,1,0,0,0,0,1,0,0 ,1,0,0,1,0,0,1,0,0,0,0,0,1,0,0,1,0,0,1,0,1,0,1,0};
unsigned int MUTE[49] = {0,1,0,0,0,1,0,1,0,1,0,0,1,0,0,1,0,1,0,0,0,0,1,0,0 ,0,1,0,1,0,0,1,0,0,0,0,1,0,0,0,1,0,0,1,0,1,0,1,0};
unsigned int SOURCE[49] = {0,1,0,0,0,1,0,1,0,1,0,0,1,0,0,1,0,1,0,0,0,0,1,0,0 ,1,0,1,0,0,0,1,0,0,0,0,0,0,1,0,1,0,0,1,0,1,0,1,0};
unsigned int SEEK_UP[49] = {0,1,0,0,0,1,0,1,0,1,0,0,1,0,0,1,0,1,0,0,0,0,1,0,0 ,0,1,0,1,0,1,0,0,0,0,0,1,0,0,0,0,1,0,1,0,1,0,1,0};
unsigned int TRK_UP[49] = {0,1,0,0,0,1,0,1,0,1,0,0,1,0,0,1,0,1,0,0,0,0,1,0,0 ,1,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,1,0,1,0,1,0};
unsigned int TRK_DN[49] = {0,1,0,0,0,1,0,1,0,1,0,0,1,0,0,1,0,1,0,0,0,0,1,0,0 ,0,1,0,0,1,0,0,0,0,0,1,0,0,1,0,0,1,0,1,0,1,0,1,0};
unsigned int DISC_UP[49] = {0,1,0,0,0,1,0,1,0,1,0,0,1,0,0,1,0,1,0,0,0,0,1,0,0 ,1,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,1,0,1,0,1,0,1,0};
unsigned int DISC_DN[49] = {0,1,0,0,0,1,0,1,0,1,0,0,1,0,0,1,0,1,0,0,0,0,1,0,0 ,0,0,1,0,1,0,0,0,0,0,1,0,1,0,0,0,1,0,1,0,1,0,1,0};
unsigned int ANSWER[49] = {0,1,0,0,0,1,0,1,0,1,0,0,1,0,0,1,0,1,0,0,0,0,1,0,0 ,0,1,0,0,0,1,0,0,0,1,0,1,0,0,1,0,1,0,0,1,0,1,0,0};
void setup()
{
pinMode(outputPin, OUTPUT);
digitalWrite(outputPin, HIGH);
pinMode(in1, INPUT);
pinMode(in2, INPUT);
pinMode(in3, INPUT);
pinMode(in4, INPUT);
pinMode(in5, INPUT);
pinMode(in6, INPUT);
pinMode(in7, INPUT);
digitalWrite(in1, HIGH); // turn on internal pullups
digitalWrite(in2, HIGH);
digitalWrite(in3, HIGH);
digitalWrite(in4, HIGH);
digitalWrite(in5, HIGH);
digitalWrite(in6, HIGH);
digitalWrite(in7, HIGH);
Serial.begin(9600);
Serial.println("start");
}
void loop()
{
check_switches();
delay(100);
}
void check_switches()
{
pinMode(in1, OUTPUT); // set the left common to input
digitalWrite(in1, LOW); // then bring it low
int read = 0;
read = digitalRead(in2); // now read 4 of the switches
if(read == 0)
{
Serial.println("+");
send(VOL_UP);
}
read = digitalRead(in3);
if(read == 0)
{
Serial.println("audio");
send(MUTE);
}
read = digitalRead(in4);
if(read == 0)
{
Serial.println("-");
send(VOL_DN);
}
read = digitalRead(in7);
if(read == 0)
{
Serial.println("up");
send(TRK_UP);
}
pinMode(in1, INPUT); // set the left common back to input so it floats
pinMode(in7, OUTPUT); // set the right common to input
digitalWrite(in7, LOW); // then bring it low
read = digitalRead(in5);
if(read == 0)
{
Serial.println("dn");
send(TRK_DN);
}
read = digitalRead(in6);
if(read == 0)
{
Serial.println("sel");
send(SOURCE);
}
digitalWrite(in7, HIGH); // bring right common back up
pinMode(in7, INPUT);
}
boolean send(unsigned int *command)
{
digitalWrite(outputPin, LOW);
delay(10);
digitalWrite(outputPin, HIGH);
delayMicroseconds(4500);
for (int x = 0; x < 49; x++)
{
if (command[x] == 0)
{
digitalWrite(outputPin, LOW);
}
else
{
digitalWrite(outputPin, HIGH);
}
delayMicroseconds(1000);
digitalWrite(outputPin, HIGH);
delayMicroseconds(200);
}
}
---------------
아두이노 소스코드
---------------
이건 아두이노를 이용해서 좀 더 쉽게 만든 예이네요.
아래 프로그램 예시는 위의 링크에서 볼 수 있습니다. 시간이 지나면 링크가 자꾸 끊어지게 되어 부득이 카피를 해왔습니다. 오해를 사지 않기 위해 출처를 분명히 밝힙니다.
-------------------------------
SAE J1979의 PID 송수신 처리
참조 사이트 : http://opengauge.googlecode.com/svn/trunk/obduino/
이 자료는 OBD-II 인터페이스 중에서, K Line Interface MC33290을 사용하는 진단기에서,
ISO 9141 표준으로 K-Line 인터페이스를 초기화, 송신, 수신하는 함수에 대한 설명이다.
ISO 9141의 연결 방법 (적색은 ECU의 연결 응답)
1(300ms) -> 33(1060ms) -> 55(0.96) -> 08(0.96) -> 08(0.96) -> 7F(0.96) -> CC(0.96) [ms]
대기 연결요청(5 bps) 응답1 응답2 응답3 연결OK 응답4
// 포트를 정의
#define K_IN 2
#define K_OUT 3
#define TOPLEFT 0
#define TOPRIGHT 1
#define BOTTOMLEFT 2
#define BOTTOMRIGHT 3
// LCD 핀의 정의
이 값을 바꾸면, 아듀이노 기판에 연결하는 LCD의 포트를 바꿀 수 있다.
#define DIPin 4 // LCD RS 핀
#define EnablePin 5 // lCD ENB 핀
#define DB4Pin 7
#define DB5Pin 8
#define DB6Pin 12
#define DB7Pin 13
#define ContrastPin 6 // LCD의 명암을 누름단추로 조절
#define BrightnessPin 9 // LCD의 밝기를 누름단추로 조절
// 포트를 설정
pinMode(K_OUT, OUTPUT); // ISO 9141 TX 핀
pinMode(K_IN, INPUT); // ISO 9141 RX 핀
pinMode( lbuttonPin, INPUT ); // 누름 단추 3 개는 입력
pinMode( mbuttonPin, INPUT );
pinMode( rbuttonPin, INPUT );
digitalWrite( lbuttonPin, HIGH); // 누름단추 포트용 내부 풀업 (포트핀의 풀업저항이 필요없다)
digitalWrite( mbuttonPin, HIGH);
digitalWrite( rbuttonPin, HIGH);
-----------------------------------------------------------------------------------------------------------------
파라메터, LCD를 초기화
if(params_load() ==0) // 설정값이 없으면, 다음과 같이 초기값을 저장
{
params.contrast =40;
params.useMetric =1;
params.perHourSpeed =20;
params.vol_eff =80; // 연료 효율 =80%, MPA을 위한 근접에 필요
params.eng_dis =20; // 엔진 크기 =2.0L
params.trip_dist =0.0;
params.trip_fuel =0.0;
params.screen[0].corner[TOPLEFT] =FUEL_CONS;
params.screen[0].corner[TOPRIGHT] =TRIP_CONS;
params.screen[0].corner[BOTTOMLEFT] =ENGINE_RPM;
params.screen[0].corner[BOTTOMRIGHT] =VEHICLE_SPEED;
params.screen[1].corner[TOPLEFT] =TRIP_CONS;
params.screen[1].corner[TOPRIGHT] =TRIP_DIST;
params.screen[1].corner[BOTTOMLEFT] =COOLANT_TEMP;
params.screen[1].corner[BOTTOMRIGHT] =CAT_TEMP_B1S1;
params.screen[2].corner[TOPLEFT] =PID_SUPPORT20;
params.screen[2].corner[TOPRIGHT] =PID_SUPPORT40;
params.screen[2].corner[BOTTOMLEFT] =PID_SUPPORT60;
params.screen[2].corner[BOTTOMRIGHT] =OBD_STD;
}
-----------------------------------------------------------------------------------------------------------------
ISO 9141 포트를 초기화
ISO 9141의 인터페이스 방법은 PID를 송수신하는 방법으로 설명됩니다.
byte iso_init()
{
byte b;
digitalWrite(K_OUT, HIGH); // K핀을 300ms 동안 HIGh로 만듭니다, (ECU 대기)
delay(300);
digitalWrite(K_OUT, LOW); // K핀을 200ms 동안 LOW로 만듭니다, 5bps 시작비트
delay(200);
b =0x33; // 0x33 즉 00110011를 송신, 5 bps 데이터 비트 8개
for (byte mask = 0x01; mask; mask <<= 1) // mask값 0x01을 0x80 이 될 때 까지 8번 반복 (LSB 우선송신)
{
if (b & mask) digitalWrite(K_OUT, HIGH); // 비트 1을 송신
else digitalWrite(K_OUT, LOW); // 비트 0을 송신
delay(200); // 비트의 간격은 5 bps로 200ms 이다 (아직은 ISO 9141 송신함수를 사용할 수 없다)
}
digitalWrite(K_OUT, HIGH); // 정지 비트의 송신, 200ms 이 필요,
delay(200); // 5 bps 정지 비트
delay(60); // 60ms 추가로 대기한다 (ISO 표준 값이다) 이제 ECU와 ISO9141 통신이 가능하다.
b =iso_read_byte(); // 이제 10400 pcs로 ECU의 데이터를 기다린다
if (b !=0x55) return -1; // ECU에서 응답하는 첫 데이터가 0x55 가 아니면 연결 실패다.
delay(5);
b=iso_read_byte(); // 첫 데이터가 0x55이면, ECU의 다음 데이터를 기다린다.
if (b !=0x08) return -1; // 두번째 데이터가 0x08이 아니면 연결 실패이다.
delay(20);
b =iso_read_byte(); // 두번째 데이터가 0x08 이면, ECU의 다음 데이터를 기다린다.
if(b!=0x08) return -1; // 세번째 데이터가 0x08 이면, 연결이 성공, ECU로 응답을 해야 한다
delay(25);
iso_write_byte(0xF7); // ECU의 응답이 성공이라는 확인으로 0xF7을 송신한다
delay(25);
b =iso_read_byte(); // ECU로 부터 마지막 응답 0xCC가 들어 오면 연결괸 것이다. (연결 성공)
if(b !=0xCC) return -1; // ISO 9141 연결이 실패이면 -1을 가지고 나간다
return 0; // ISO 9141 연결이 성공이면 0을 가지고 나간다
}
이제 ISO 9141 연결에 성공하였으므로, ISO 9141 송/수신 함수를 사용할 수 있다.
-----------------------------------------------------------------------------------------------------------------
ISO 9141 송신 함수
#define _bitPeriod 1000000L/10400L // ISO 9141의 1 비트 주기는 10400 bps = 1000000/10400 = 96
void iso_write_byte(byte b)
{
int bitDelay = _bitPeriod - clockCyclesToMicroseconds(50); // 비트주기 -digitalWrite의 처리시간을 보정
digitalWrite(K_OUT, LOW);
delayMicroseconds(bitDelay); // ISO 9141 연결핀 K에 LOW를 1비트 송신한다 (시작 비트)
for (byte mask = 0x01; mask; mask <<= 1) // LSB를 우선으로 8 비트를 송신 (무조건 송신)
{
if (b & mask) digitalWrite(K_OUT, HIGH); // 비트 1을 1개 송신
else digitalWrite(K_OUT, LOW); // 비트 0을 1개 송신
delayMicroseconds(bitDelay);
}
digitalWrite(K_OUT, HIGH); // ISO 9141에 연결된 핀 K에 HIGH를 송신 (정지 비트)
delayMicroseconds(bitDelay); // ISO 9141는 1선으로 송수신하므로 송신할 때는 수신이 안된다.
}
-----------------------------------------------------------------------------------------------------------------
ISO 9141 수신함수
int iso_read_byte()
{
int val = 0;
unsigned long timeout;
int bitDelay = _bitPeriod - clockCyclesToMicroseconds(50); // 비트주기 -digitalWrite의 처리시간을 보정
timeout =millis();
while (digitalRead(K_IN)) // 시작 비트를 기다린다
{
if((millis()-timeout) > 300L) return -1; // 300ms이 지나면 수신 불량이다.
}
if (digitalRead(K_IN) == LOW) // 처음으로 LOW가 나타나면, 이제부터 수신이 시작된다
{
delayMicroseconds(bitDelay / 2 - clockCyclesToMicroseconds(50)); // 비트폭의 중간에서 수신을 시작한다.
for (int offset =0; offset <8; offset++) // 8 비트를 수신한다, 비트0 ~ 비트7을 수신
{
delayMicroseconds(bitDelay); // 비트폭의 중간에서 비트값을 읽는다
val |=digitalRead(K_IN) << offset; // 읽은 비트를 변수에 저장하고, 좌로 1비트 시프트
}
delayMicroseconds(_bitPeriod); // 남은 비트폭을 기다린다 (다음 바이트 수신을 위함)
return val; // 수신한 8 비트 =1 바이트를 가지고 나간다. (수신 성공)
}
return -1; // 수신 실패는 -1을 가지고 나간다
}
-----------------------------------------------------------------------------------------------------------------
ISO 9141 테이터 읽기
ECU에서 응답하는 ISO 9141의 데이터 패킷을 iso_read_byte()을 호출하여 읽는다
ECU의 응답 데이터 패킷은 +header + cmd + crc로 구성된다.
수신할 바이트 갯수와 저장할 버퍼를 가지고 호출해야 한다, 데이터만 buf[20]에 저장된다.
byte iso_read_data(byte *data, byte len)
{
byte i;
byte buf[20];
// 머리문자 3 바이트: [80 + datalen] [destination = F1] [source = 01] (예: 83, F1, 01)
// 데이터 1 + len 바이트 : [40 +cmd0] [result0], CRC, (예: C1, E9, 8F, AE)
for(i=0; i<3+1+1+len; i++) // 수신할 바이트의 갯수는 5 + 데이터 길이 len 이다.
buf[i] =iso_read_byte(); // ISO 9141 에서 데이터를 바이트로 읽는다
// 머리문자 3개는 점검하지 않는다, 오류코드 0x7f와 CRC도 점검하지 않는다.
memcpy(data, buf +4, len); // 수신한 패킷에서, 데이터는 5번째 바이트 부터이다.
return len;
}
진단기가 ECU로 PID를 송신하면. (예 MODE=01,PID=00의 송신은 0x68 0x6A 0xF1 0x01 0x00 0xC4)
ECU가 진단기로 응답한다. (예 응답은 0x48 0x6B 0x41 0x01 XX XX XX XX CRC 이다, XX는 응답 데이터)
송신 머리문자 0x68 0x6A 0xF1는 진단기에서 PID를 송신할 때 추가되며,
수신 머리문자 0x48 0x6B 0x41 0x01는 ECU에서 응답할 때 추가된다. (0x41은 ECU 번호+0x40, 0x01은 요청한 PID)
-----------------------------------------------------------------------------------------------------------------
ISO 9141 테이터 쓰기
송신버퍼 data에 저장된 데이터에 송신용 머리문자 3개를 추가하고,
송신 데이터의 마지막에는 CRC를 만들어 추가한 다음에.
송신버퍼에 들아있는 모든 문자를 iso_write_byte() 함수를 반복 호출하여 송신한다.
byte iso_write_data(byte *data, byte len)
{
byte i, n;
byte buf[20];
buf[0] =0x68; // ISO 머리문자(header)
buf[1] =0x6A; // 0x68 0x6A 는 OBD-II 의 요청 명령이다t
buf[2] =0xF1; // 진단기의 주소 (송신주소)
for (i =0; i<len; i++) buf[i+3] =data[i]; // 머리문자는 3개 뒤에 데이터를 추가한다
i +=3;
buf[i] =iso_checksum(buf, i); // 송신 CRC를 만든다음 송신버퍼의 마지막에 추가한다
n =i +1; // 송신 바이트 갯수에 CRC를 추가한다
for(i =0; i <n; i++)
{
iso_write_byte(buf[i]); // 1 바이트씩 송신버퍼를 ISO 9141 인터페이스로 송신한다
delay(20); // 1 바이트 마다 20ms 지연시간을 준다.
}
return 0;
}
-----------------------------------------------------------------------------------------------------------------
SAE J1979의 CRC생성 함수
J1979의 CRC는 머리,,, 데이터끝 까지 0x00에 모두 더한 값이며, 덧셈에서 발행하는 자리올림은 사용하지 않고 버린다.
J1979의 CRC는 송,수신 패킷의 마지막에 추가 되어야 하며, 주로 송신 패킷에서 사용한다.
byte iso_checksum(byte *data, byte len)
{
byte i;
byte crc;
crc=0; // CRC의 초기값은 0 이다.
for(i=0; i <len; i++) crc=crc +data[i]; // 송신버퍼에 들은 데이터를 모두 CRC에 더한다
return crc;
}
-----------------------------------------------------------------------------------------------------------------
SAE J1979의 PID 읽기 함수
참조 사이트 : http://opengauge.googlecode.com/svn/trunk/obduino/
이 자료는 OBD-II 인터페이스 중에서, K Line Interface MC33290을 사용하는 진단기에서,
SAE J1979 표준 PID를 송신하고, 응답하는 데이터를 수신하는 PID 함수이다.
이 함수를 실행하려면, ECU로 요청하는 PID 번호 와 ECU의 응답을 저장하는 32비트 버퍼가 필요하다.
PID 번호는 ISO송신 함수를 실행하여 ECU로 전송되고, ISO수신 함수에서 받은 데이터는 지정된 retbuf에 저장한다.
unsigned long pid01to20_support;
unsigned long pid21to40_support;
unsigned long pid41to60_support;
long get_pid(byte pid, char *retbuf)
{
byte i;
byte cmd[2]; // 송신 PID를 저장하는 변수
byte buf[10]; // 수신 데이터를 저장하는 변수
long ret; // 결과를 저장하는 4바이트 변수
byte reslen; // 결과의 길이를 저장하는 변수
char decs[16]; // 결과를 문자로 변환하는 작업용 변수
if( pid !=0x00) // 0x00 =PID_SUPPORT20 , PID 요청값이 PID를 지원하는지 확인한다.
{
if( (pid <=0x20 && ( 1L <<(0x20 -pid) & pid01to20_support ) == 0 )
|| (pid >0x20 && pid <=0x40 && ( 1L <<(0x40-pid) & pid21to40_support ) == 0 )
|| (pid >0x40 && pid <=0x60 && ( 1L <<(0x60-pid) & pid41to60_support ) == 0 )
|| (pid >LAST_PID) )
{
sprintf_P(retbuf, PSTR("%02X N/A"), pid); // PID 확인결과를 "아님"으로 표시
return -1; // 아닌 경우는 나머지를 실해하지 않고 이 함수를 끝낸다.
}
}
reslen =pgm_read_byte_near(pid_reslen+pid); // PID 번호에 따르는 데이터의 길이를 구한다
cmd[0] =0x01; // ISO 명령은 1 바이트이다
cmd[1] =pid; // 요청된 PID값을 변수에 저장
iso_write_data(cmd, 2); // ISO 송신함수를 실행, PID값을 ECU로 송신.
iso_read_data(buf, reslen); // ISO 수신함수를 실행, ECU에서 응답한 데이터를 수신.
switch(pid) // SAE 표준 공식과 단위를 사용, PID 응답을 분리하고 계산한다.
{
case 0x0C: // ENGINE_RPM =엔진 회전수
ret=(buf[0]*256U+buf[1])/4U;
sprintf_P(retbuf, PSTR("%ld RPM"), ret);
break;
case 0x10: // MAF_AIR_FLOW =흡기관 공기속도
ret=buf[0]*256U+buf[1];
int_to_dec_str(ret, decs, 2); // not divided by 100 for return value!!
sprintf_P(retbuf, PSTR("%s g/s"), decs);
break;
case 0x0D: // VEHICLE_SPEED =차량 속도
ret=buf[0];
if(!params.useMetric) ret=(ret*621U)/1000U;
sprintf_P(retbuf, PSTR("%ld %s"), ret, params.useMetric?"ꠗꠙ":"ꠚꠙ");
break;
case 0x03: // FUEL_STATUS =연료 상태
ret=buf[0] *256U +buf[1];
if(buf[0]==0x01) sprintf_P(retbuf, PSTR("OPENLOWT")); // 엔진 온도 낮음
else if(buf[0]==0x02) sprintf_P(retbuf, PSTR("CLSEOXYS")); // Closed loop, 산소 센서값을 연료혼합으로 되돌린다.
else if(buf[0]==0x04) sprintf_P(retbuf, PSTR("OPENLOAD")); // Open loop 엔진 부하 [%]
else if(buf[0]==0x08) sprintf_P(retbuf, PSTR("OPENFAIL")); // Open loop 고장난 장치
else if(buf[0]==0x10) sprintf_P(retbuf, PSTR("CLSEBADF")); // Closed loop, 최근에 고장난 장치의 산소센서
else sprintf_P(retbuf, PSTR("%04lX"), ret);
break;
// 다음 12개의 PID 결과는 1 바이트로 모두 [%] 형식이다.
case 0x04: // LOAD_VALUE =엔진 부하
case 0x11: // THROTTLE_POS =가속발판 위치
case 0x45: // REL_THR_POS =가속발판 위치 %값
case 0x2C: // EGR =배기 혼합장치
case 0x2D: // EGR_ERROR =배기 혼합장치 오류
case 0x2F: // FUEL_LEVEL =연료 잔량
case 0x47: // ABS_THR_POS_B =미끄럼방지 가속기 위치 B
case 0x48: // ABS_THR_POS_C =미끄럼방지 가속기 위치 C
case 0x49: // ACCEL_PEDAL_D =가속 D
case 0x4A: // ACCEL_PEDAL_E =가속 E
case 0x4B: // ACCEL_PEDAL_F =가속 F
case 0x4C: // CMD_THR_ACTU =가속 구동값:
ret=(buf[0] *100U) /255U; // 0~255 범위의 결과를 [%]로 계산한다.
sprintf_P(retbuf, PSTR("%ld %%"), ret); // %값을 10진으로 변환하고, ret 변수에 저장
// 다음 8개의 PID 응답은 [mV]로 변환한다.
case 0x14: // B1S1_O2_V =산소센서 1-1 전압
case 0x15: // B1S2_O2_V =산소센서 1-2 전압
case 0x16: // B1S3_O2_V =산소센서 1-3 전압
case 0x17: // B1S4_O2_V =산소센서 1-4 전압
case 0x18: // B2S1_O2_V =산소센서 2-1 전압
case 0x19: // B2S2_O2_V =산소센서 2-2 전압
case 0x1A: // B2S3_O2_V =산소센서 2-3 전압
case 0x1B: // B2S4_O2_V =산소센서 2-4 전압
ret =buf[0] *5U; // 결과를 1000으로 나누지 않음
if(buf[1] ==0xFF) sprintf_P(retbuf, PSTR("%ld mV"), ret); // 첫 바이트 = 0xFF 이면, 결과를 바로 10진으로 저장
else sprintf_P(retbuf, PSTR("%ldmV/%d%%"), ret, ((buf[1]-128)*100)/128); // 0xFF가 아니면, 결과를 계산하고 저장
break; // 결과의 첫 바이트 최상위 비트가 1이 아니면, 나머지 7비트를 [%]로 계산한다.
case 0x21: // DIST_MIL_ON =계기판의 엔진고장 등을 켠다
case 0x31: // DIST_MIL_CLR =계기판의 엔진고장 등을 끈다
ret =buf[0] *256U +buf[1]; // 결과 1 바이트 2 개를, 2바이트 정수로 변환한다.
if(!params.useMetric) ret =(ret *621U) /1000U; // 저장된 2바이트 정수를 *621/1000으로 계산한다
sprintf_P(retbuf, PSTR("%ld %s"), ret, params.useMetric?"ꠗ":"mi"); // 10진으로 변환해서 문자로 저장한다
break;
case 0x4D: // TIME_MIL_ON =고장표시등이 점등된 시간
case 0x4E: // TIME_MIL_CLR =고장 표시등이 소등된 시간
ret=buf[0] *256U +buf[1]; // 2 바이트 정수로 변환
sprintf_P(retbuf, PSTR("%ld min"), ret); // 10진 문자로 변환한 시간을 분(min)으로 표시한다
break;
case 0x05: // COOLANT_TEMP =냉각수 온도
case 0x0F: // INT_AIR_TEMP =흡기관 온도
case 0x46: // AMBIENT_TEMP =주위 온도
case 0x3C: // CAT_TEMP_B1S1 =환원장치 온도 #1-1
case 0x3D: // CAT_TEMP_B2S1 =환원장치 온도 #2-1
case 0x3E: // CAT_TEMP_B1S2 =환원장치 온도 #1-2
case 0x3F: // CAT_TEMP_B2S2 =환원장치 온도 #2-2
if(pid >=0x3C && pid <=0x3F) // 산소센서의 온도를 계산
ret =(buf[0] *256U +buf[1]) /10U -40; // 10으로 나누고 40을 뺀다음, 온도로 저장한다 .
else ret =buf[0] -40; // 산소센서가 아니면 40을 뺀다음, 온도로 저장한다.
if(!params.useMetric) ret =(ret *9) /5 +32; // 선택한 단위가 Metric 이면 섭씨로 변환한다
sprintf_P(retbuf, PSTR("%ldꠛ%c"), ret, params.useMetric?'C':'F'); // 10진 문자로 단위와 같이 저장한다,
break;
case 0x06: // STF_BANK1 =단시간 연료 %조절 #1
case 0x07: // LTR_BANK1 =장시간 연료 %조절 #1
case 0x08: // STF_BANK2 =단시간 연료 %조절 #2
case 0x09: // LTR_BANK2 =장시간 연료 %조절 #2
ret=(buf[0]-128) *7812; // 첫 바이트 최상위 비트를 지우고, 7 비트(A6~A0)에 7812를 곱한다음 저장한다.
int_to_dec_str(ret/100, decs, 2); // 결과를 100으로 나누고, 10진 문자 2자리로 변환한다, (10000 으로 나누지 않음)
sprintf_P(retbuf, PSTR("%s %%"), decs);
break;
case 0x0A: // FUEL_PRESSURE =연료분사 압력
case 0x0B: // MAN_PRESSURE =분기관 압력
case 0x33: // BARO_PRESSURE =공기 압력
ret=buf[0];
if(pid ==0x0A) ret *=3U; // 만일 응답값이 FUEL_PRESSURE 이면, 3을 곱해서 저장한다.
sprintf_P(retbuf, PSTR("%ld kPa"), ret); // 표시하는 값은 kPa(압력의 단위) 이다
break;
case 0x0E: // TIMING_ADV =실린더 상대 각도 [deg] (점화시기)
ret =(buf[0] /2) -64; // 응답값을 2로 나누고 -64를 한다음 저장한다.
sprintf_P(retbuf, PSTR("%ldꠛ"), ret);
break;
case 0x1C: // OBD_STD =연결된 ECU가 지원하는 OBD 표준을 나타낸다 (254 바이트)
ret =buf[0]; // 응답으로 들어온 값으로 부터, OBD 표준에 해당하는 하나의 이름을 선택하여 표시한다
if(buf[0] ==0x01) sprintf_P(retbuf, PSTR("OBD2CARB")); // 응답이 0x01 이면 OBD2 CARB 표준을 지원한다.
else if(buf[0]==0x02) sprintf_P(retbuf, PSTR("OBD2EPA")); // 응답이 0x02 이면 OBD2 EPA 표준을 지원한다.
else if(buf[0]==0x03) sprintf_P(retbuf, PSTR("OBD1&2")); // 응답이 0x03 이면 OBD1과 2 표준을 지원한다.
else if(buf[0]==0x04) sprintf_P(retbuf, PSTR("OBD1")); // 응답이 0x04 이면 OBD1 표준을 지원한다.
else if(buf[0]==0x05) sprintf_P(retbuf, PSTR("NOT OBD")); // 응답이 0x05 이면 OBD를 지원하지 않는다.
else if(buf[0]==0x06) sprintf_P(retbuf, PSTR("EOBD")); //응답이 0x06 이면 EOBD 표준을 지원한다.
else if(buf[0]==0x07) sprintf_P(retbuf, PSTR("EOBD&2")); //응답이 0x07 이면 EOBD2 표준을 지원한다.
else if(buf[0]==0x08) sprintf_P(retbuf, PSTR("EOBD&1"));//응답이 0x08 이면 EOBD1 표준을 지원한다.
else if(buf[0]==0x09) sprintf_P(retbuf, PSTR("EOBD&1&2"));//응답이 0x09 이면 EOBD1과 2 표준을 지원한다.
else if(buf[0]==0x0a) sprintf_P(retbuf, PSTR("JOBD")); //응답이 0x0A 이면 JOBD 표준을 지원한다.
else if(buf[0]==0x0b) sprintf_P(retbuf, PSTR("JOBD&2")); //응답이 0x0B 이면 JOBD2 표준을 지원한다.
else if(buf[0]==0x0c) sprintf_P(retbuf, PSTR("JOBD&1")); //응답이 0x0C 이면 JOBD1 표준을 지원한다.
else if(buf[0]==0x0d) sprintf_P(retbuf, PSTR("JOBD&1&2")); // 응답이 0x0D이면, JOBD1과 2 표준을 지원한다.
else sprintf_P(retbuf, PSTR("OBD:%02X"), buf[0]); // 위 항목에서 발견하지 못한 값이면, OBD ?? 표준을 지원한다.
break;
default: // 처리항목에 없는 데이터는 들어온 값을 처리없이 그대로 표시한다
ret=0; // (처리하지 못한 항목은, 나가는 값이 0 이다)
for(i=0; i<reslen; i++)
{
ret *=256L;
ret +=buf[i]; // 응답 1 바이트 2개를, 2 바이트 정수로 변환
}
sprintf_P(retbuf, PSTR("%08lX"), ret); // 문자로 변환하여 retbuf에 저장
break;
}
return ret; // 선택 처리된 1개 항목의 결과는 ret에 저장된다, 2 바이트 정수값을 주함수로 돌려준다
} // 수신된 데이터를 LCD에 표시하는 문자는 retbuf 에 저장되어 있다.
지금까지 OBD-II 인터페이스 ISO1941의 초기화, 송신, 수신, PID송신, PID수신 함수에 대해 설명하였습니다.
다음번엔 OBD-II 인터페이스 ISO9141 PID 처리함수와 주함수에 대해서 알아 보도록 하겟습니다.
이 프로그램은 무료 소프트웨어로, 신체와 재산 상의 어떤 위험과 손해를 보상하지 않습니다.
이 프로그램은 GNU 무료 소프트웨어 배포규정을 따릅니다.
Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
----------------------
CAN MESSAGE 형식
----------------------
아래 정보는 다음 링크에서 볼 수 있습니다. 시간이 지나면 링크가 자꾸 끊어지게 되어 부득이 카피를 해왔습니다. 오해를 사지 않기 위해 출처를 분명히 밝힙니다.
참조 사이트 : http://opengauge.googlecode.com/svn/trunk/obduino/
CAN 프로토콜에서 메시지는 최대 94bit 로 이루어 집니다. 메시지 내부에는 명시적인 주소정보는 포함되지 않습니다. 대신에 메시지의 내용 내부에 주소정보를 포함해서, 상황에 따른 주소처리를 하게 되어 있습니다. 그래서 메시지를 받은 노드는 스스로 메시지의 내용을 파악해서, 자신이 처리해야 할 메시지인지를 결정하여 응답하게 됩니다. 메시지는 4 가지 종류로 나뉩니다. 각각
데이터 프레임
원격 프레임 (Remote Frame)
오류 프레임 (Error Frame)
부하 프레임 (Overload Frame)
이 그것입니다.
데이터 프레임은 데이터를 보내기 위한 목적을 가진 프레임인데, 가장 기본적인 메시지입니다.
이 메시지는 다음과 같이 구성되게 됩니다.
우선순위 필드 (Identifier): 우선순위를 지정하는 부분입니다. (11 비트 또는 29 비트로
이루어집니다.)
데이터 필드: 0~8 개의 바이트 데이터로 이루어진 필드입니다.
CRC 필드: 15 비트의 체크썸 정보를 가지고 있습니다.
응답 필드: 메시지가 하나의 이상의 노드로 전달이 되었는지를 나타내는 필드입니다.
원격 프레임은 데이터 프레임과 비슷하지만, 우선순위 필드의 뒷부분에 원격 프레임이라는 표식을 한다는 점과, 데이터 필드가 존재하지 않는 다는 점이 다릅니다. 이 메시지의 용도는 데이터 프레임을 요청하는 것입니다. 즉 특정 노드가 데이터 프레임을 다른 노드로부터 요구하고 싶을 때 이 메시지를 보내게 됩니다. 간단한 예를 들면 노드 A 가 Identifier 를 234 로 설정한 원격 프레임을 전송하게 되면, 노드 B 가 데이터 프레임의 Identifier 필드에 234 로 설정한 후 응답하게 됩니다. 하지만 이 메시지는 많이 사용되지는 않습니다.
에러프레임은 정상적인 프레임 구조를 이루고 있지 않는 프레임입니다. 이 메시지는 에러를 감지한 노드가 다른 노드들에게 에러를 알리기 위한 것입니다. 그렇게 되면 다른 노드들도 에러를 감지하게 됩니다. 에러를 감지하게 되면 전송 중이던 노드는 전송을 재 시작 하게 됩니다.
그림처럼, 에러프레임은 미리 정해진 값인, 오류표지(Error Flag) 값을 내보냅니다. 그 후에 8 비트의 오류 구분 필드(Error Delimiter) 부분이 뒤따릅니다. 이 부분은 열성비트 8 개로 되어 있어서, 에러를 감지한 다른 노드 들이 해당 오류표지(Error Flag)값을 내보낼 수 있게 됩니다.
부하프레임은, 지금 바쁘니 나중에 메시지를 보내주라는 의미의 메시지입니다. 이는 노드 자체가 너무 바쁜 일을 처리 중이거나 해서 메시지를 처리하기 어려울 때 다른 노드들에게 지금은 메시지를 처리 할 수 없다고 알리는 메시지입니다. 현재는 이 메시지를 거의 사용되지 않습니다.
블록 다이어그램은 이 IC 의 큰 3 가지 모듈을 보여주고 있습니다. RS232 Interface 를 통해서 PC 등의 UART 통신을 하게 되고, 중앙의 명령어 처리기 (Command and Protocol Interpreter)가 가장 중요한 기능인 UART 를 통해서 사용자의 입력을 처리하여 OBD II 방식으로 신호를 보내거나 받는 일을 하는 부분입니다. 그리고 오른쪽에는 OBDII 프로토콜을 지원하기 위한 부분이 있습니다.
이 칩의 핀 배치도를 보면 CAN, ISO, PWM, VPW 를 모두 지원하도록 핀이 제공되는 것을 알 수 있습니다. 또한 OBD 의 전송상태, RS232 의 전송상태를 LED 로 보여주기 위한 핀도 제공함을 알 수 있습니다. ELM327 은 자체적으로 RS-232 모니터 프로그램이 존재하며, UART 케이블을 통해서 AT 커맨드를 보내면 그것을 분석해서 처리하고, OBD II 프로토콜을 통해서 전송을 해 주게 됩니다.
참고
OBD-II PIDs
https://en.wikipedia.org/wiki/OBD-II_PIDs
Generic/Manufacturer OBD2 Codes and Their Meanings
http://www.totalcardiagnostics.com/support/Knowledgebase/Article/View/21/0/genericmanufacturer-obd2-codes-and-their-meanings
|
첫댓글 와~정말 좋은 것이네요
주욱 읽다가 패스해버렸네요 ㅎㅎ
이렇게 볼수 있다면...상상만으로도 좋네요
다른건 괜찮은데 이것도 나름 스캐너라서 가끔 시그널 오류나거나 하면 ecu나 tcu가 리셋 됩니다.
오토미션 차량일 경우 잘가다가 갑자기 쿵 하면서 기어가 쉬프트다운되버리는데
주행중 obd간섭으로 TCU가 리셋되면서 나타나는 증상입니다.
OBD 참 오랜만에 들어봅니다. 글 잘보았습니다.
엄청난 정보네요.. 이걸 다 읽고 이해 하려면 1주일 이상 걸릴듯 합니다.^^
덕분에 OBD에 관하여 배우고 갑니다.^^
그렇더군요. pid값이 서로 다르고, 또 돈받고 판다고 하네요.