Kết nối cổng OBD2 với máy tính

pearl

Điều Hành Viên
Thành viên BQT
Xin chào các bạn sinh viên. Mình làm series về điện tử ô tô dành cho các bạn sinh viên thích tìm tòi khám phá. Hy vọng thông tin cung cấp sẽ giúp ích cho các bạn trong quá trình học tập.
Bài đầu tiên xin chia sẻ với các bạn cách kết nối Arduino với canbus của ô tô thông qua cổng OBD2 để đọc các thông số tốc độ, vòng tua, nhiệt độ, … có rất nhiều thông số có thể đọc qua can bus nhưng hiện tại trong ví dụ này mình chỉ đọc vòng tua và nhiệt độ nước làm mát thôi nhé. Các thông số khác đọc tương tự.
Lý do mình chọn arduino vì đây là module rất thông dụng, có rất nhiều thư viện hỗ trợ. Người sử dụng hầu như chỉ cần ghép nối các thư viện, chỉnh sửa chút ít thông tin là có thể hoàn thành đồ án một cách nhanh chóng.
Arduino là module dựa vào vi điều khiển AVR nhung câu lệnh đã được chuyển thế sang ngôn ngữ C dễ nhớ hơn.
Nhưng riêng Arduino due là dùng chip của ARM nhé.
Các bước chuẩn bị như sau.

1. Mua mạch Arduino UNO, module can bus loại kết nối trực tiếp với Arduino (mình dùng Sunflower can shield V3).

1580887738029.png1580887747574.png

Canbus này sử dụng controller MCP2515 và bộ nhận TJA1050.
Các thư viện đều viết cho tần số thạch anh 16 trong khi mạch CAN bus đang dùng thạch anh 8. Vì vậy sau khi mua về các bạn phải thay bằng thạch anh 16 thì mới chạy được.
Các bạn có thể dùng module có hình dạng sau, cách giao tiếp vẫn tương tự.

1580887791773.png

Các bạn lưu ý hai module này giao tiếp với Arduino qua chuẩn SPI nhé.
Hai module này đều mua được trên web tại thị trường Việt Nam. Module sunflower CAN shield tiện hơn vì không phải nối dây giữa Arduino.
Luu ý khi mua Arduino thì phải mua cả cáp nạp nhé. Arduino nạp luôn trên mạch nên không cần mạch nạp. Arduino quá thông dụng rồi nên mình không giới thiệu nhiều nữa.

2. Sau khi các bạn có các module này rồi, bước tiếp theo sẽ tải IDE của Arduino. Đây là giao diện lập trình cho Arduino bằng ngôn ngữ C rất trực quan.
Link tải phần mềm: https://www.arduino.cc/en/main/software
Hoặc lên google search: “IDE Arduino” sẽ có.
Phần mềm này khá nhẹ (vài trăm MB), có nhiều phiên bản và các bạn dùng verson nào cũng được.

3. Hướng dẫn sử dụng Arduino. Các bạn tải Arduino cook book theo link sau (bản tiếng anh).
Link: https://juniorfall.files.wordpress.com/2011/11/arduino-cookbook.pdf
Các bạn học hết được hướng dẫn sử dụng này là trở thành MASTER rồi.

4. Các thư viện cần có.
Thư viện tải từ Github theo đường link: https://github.com/Mebus/CAN_BUS_Shield-1

5. Lý thuyết về OBD2. Các bạn them khảo link sau nhé. Thông tin trong link này sẽ giúp các bạn hiểu được cách tính toán vòng tua, nhiệt độ… sau khi đọc được thông tin. Vì thông tin đọc được là giá trị nguyên sơ, cần phải đưa thông tin vào tính toán mới ra được giá trị.
Link: https://en.wikipedia.org/wiki/OBD-II_PIDs
Ví dụ: ta sẽ đọc được giá trị A và B thông quá Can bus. Vòng tua RPM sẽ được tính qua công thức (256A+B)/4.

1580888051782.png

Vậy A/B là các giá trị nào? Các bạn tham khảo link sau.
https://www.csselectronics.com/screen/page/simple-intro-obd2-explained/language/en

1580888079867.png

Bởi vì giá trị đọc về được là giá trị 8 byte (64 bit). A sẽ là giá trị byte thứ 3, B là giá trị tại byte thứ 4.
Nhân tiện chúng ta tham khảo cấu trúc bức điện của 1 tin nhắn, giải thích như sau.
Bức điện request: 7DF 02 01 0D 55 55 55 55 55
Bức điện trả lời: 7E8 03 41 0D 32 xx xx xx xx

- Identifier:
7DF: định nghĩa đây là bức điện yêu cầu thông tin
7E8 à 7EF: định nghĩa đây là bức điện hồi đáp.
- Byte (length)
02 (thông tin bao gồm 02 dữ liệu: mode và địa chỉ thông tin 01 và 0D): cho bức điện request
03 (bởi vì lúc này thông tin bao gồm 03 dữ liệu: mod, địa chỉ và dữ liệu phản hồi): cho bức điện phản hồi.
- Mode
Request: là 01
Response: 41 (chuyển 0à4)
- PID: chứa địa chỉ các biến cần lấy thông tin.
Ví dụ: RPM thì địa chỉ lấy thông tin là 0C, speed địa chỉ lấy thông tin là 0D.
- Ah, Bh,Ch,Dh: dữ liệu đọc về lưu dưới dạng HEX
6. Sơ đồ kết nối.

  • Nếu các bạn dùng Sunflower CAN shield thì cách kết nối rất đơn giản, chỉ ghép hai module vào mới nhau như hình bên dưới. Kết nối qua cổng com có sẵn trên CAN bus shield như hình chụp. Nhược điểm của kết nối này là mạch Arduino UNO dùng nguồn 12V từ xe nên IC nguồn rất nóng. Các bạn có thể dùng nguồn ngoài <12 để thay thế.
  • Với cách này phải làm thêm cáp chuyển đổi DB9-OBD2. Cáp này có trên web nhưng phải mua từ nước ngoài. Giải pháp khác là mua riêng cáp OBD2 loại thường, rồi mua thêm cổng DB9 rời để hàn vào đầu còn lại.
1580888222289.png1580888229814.png

Nếu không có cổng DB9 có thể kết nối vào domino như bên dưới. cấp nguồn cho Arduino là CAN shield có nguồn, không cần phải cấp hai mạch riêng.

1580888245923.png

Nếu các bạn dùng module rời nhứ hình dưới thì cách kết nối như sau.
1580888264328.png

1580888301482.png

Để kiểm tra lại sơ đồ của Arduino uno, các bạn có thể vào google tra từ “Arduino uno schematic”
 

pearl

Điều Hành Viên
Thành viên BQT
Mình tiếp tục đăng phần 2 nhé.

7. Giải thích một số câu lệnh quan trọng.
- #define PID_COOLANT_TEMP 0x05 //địa chỉ để lấy dữ liệu nhiệt đồ
- #define CAN_ID_PID 0x7DF //identifier 0x7DF để chỉ ra đây là bứ điện yêu cầu thông tin.
- unsigned char tmp[8] = {0x02, 0x01, __pid, 0, 0, 0, 0, 0};
--pid: chưa địa chỉ dữ liệu cần yêu cầu.
Các thông tin khác tham khảo mục trên.
- CAN.sendMsgBuf(CAN_ID_PID, 0, 8, tmp);
CAN_ID_PID: địa chỉ xuất phát của dữ liệu
0: standard frame, 1-extended frame
8: chiều dài dữ liệu là 8 byte
Tmp: nội dung của tin nhắn.
- CAN.readMsgBuf(&len, buf);
&len: chiều dài dữ liệu
Buf: bộ đệm chứa dữ liệu
- while (CAN_OK != CAN.begin(CAN_500KBPS))
Khởi tạo giao thức can bus với tốc độ 500KBPS.

8. Chương trình chính.
#include <SPI.h>
#include "mcp_can.h"

/*SAMD core*/
#ifdef ARDUINO_SAMD_VARIANT_COMPLIANCE
#define SERIAL SerialUSB
#else
#define SERIAL Serial
#endif

// the cs pin of the version after v1.1 is default to D9
// v0.9b and v1.0 is default D10
const int SPI_CS_PIN = 10;

MCP_CAN CAN(SPI_CS_PIN); // Set CS pin

#define PID_ENGIN_PRM 0x0C
#define PID_VEHICLE_SPEED 0x0D
#define PID_COOLANT_TEMP 0x05

#define CAN_ID_PID 0x7DF

unsigned char PID_INPUT;
unsigned char getPid = 0;

void set_mask_filt()
{
/*
* set mask, set both the mask to 0x3ff
*/
CAN.init_Mask(0, 0, 0x7FC);
CAN.init_Mask(1, 0, 0x7FC);

/*
* set filter, we can receive id from 0x04 ~ 0x09
*/
CAN.init_Filt(0, 0, 0x7E8);
CAN.init_Filt(1, 0, 0x7E8);

CAN.init_Filt(2, 0, 0x7E8);
CAN.init_Filt(3, 0, 0x7E8);
CAN.init_Filt(4, 0, 0x7E8);
CAN.init_Filt(5, 0, 0x7E8);
}

void sendPid(unsigned char __pid)
{
unsigned char tmp[8] = {0x02, 0x01, __pid, 0, 0, 0, 0, 0};
SERIAL.print("SEND PID: 0x");
SERIAL.println(__pid, HEX);
CAN.sendMsgBuf(CAN_ID_PID, 0, 8, tmp);
}

void setup()
{
SERIAL.begin(115200);
while (CAN_OK != CAN.begin(CAN_500KBPS)) // init can bus : baudrate = 500k
{
SERIAL.println("CAN BUS Shield init fail");
SERIAL.println(" Init CAN BUS Shield again");
delay(100);
}
SERIAL.println("CAN BUS Shield init ok!");
set_mask_filt();
}


void loop()
{
sendPid(PID_ENGIN_PRM);
delay(200);
taskCanRecv1();
delay(500);
sendPid(PID_COOLANT_TEMP);
delay(100);
taskCanRecv2();
delay(200);
taskDbg();

if(getPid) // GET A PID
{
getPid = 0;
sendPid(PID_INPUT);
PID_INPUT = 0;
}
}

void taskCanRecv1()
{
unsigned char len = 0;
unsigned char buf[8];

if(CAN_MSGAVAIL == CAN.checkReceive()) // check if get data
{
CAN.readMsgBuf(&len, buf); // read data, len: data length, buf: data buf

SERIAL.println("\r\n------------------------------------------------------------------");
SERIAL.print("Get Data From id: 0x");
SERIAL.println(CAN.getCanId(), HEX);
for(int i = 0; i<len; i++) // print the data
{
SERIAL.print("0x");
SERIAL.print(buf);
SERIAL.print("\t");
}
SERIAL.println("\n");
SERIAL.print("toc do...:");
SERIAL.println((buf[3]*256+buf[4])/4);
}
}
void taskCanRecv2()
{
unsigned char len = 0;
unsigned char buf[8];

if(CAN_MSGAVAIL == CAN.checkReceive()) // check if get data
{
CAN.readMsgBuf(&len, buf); // read data, len: data length, buf: data buf

SERIAL.println("\r\n------------------------------------------------------------------");
SERIAL.print("Get Data From id: 0x");
SERIAL.println(CAN.getCanId(), HEX);
for(int i = 0; i<len; i++) // print the data
{
SERIAL.print("0x");
SERIAL.print(buf);
SERIAL.print("\t");
}
SERIAL.println("\n");
SERIAL.print("nhiet do...:");
SERIAL.println(buf[3]-40);
}
}
void taskDbg()
{
while(SERIAL.available())
{
char c = SERIAL.read();

if(c>='0' && c<='9')
{
PID_INPUT *= 0x10;
PID_INPUT += c-'0';

}
else if(c>='A' && c<='F')
{
PID_INPUT *= 0x10;
PID_INPUT += 10+c-'A';
}
else if(c>='a' && c<='f')
{
PID_INPUT *= 0x10;
PID_INPUT += 10+c-'a';
}
else if(c == '\n') // END
{
getPid = 1;
}
}
}
// END FILE

9. Hình ảnh khi chạy chương trình

1580889854125.png

10. Phát triển ứng dụng.
Mình viết bài này chỉ mang tính chất định hướng, các bạn có thể phát triển các ứng dụng sau.

  • Kết hợp với module Bluetooth để nhận thông tin trên điện thoại, khi cần truy cập data thì không phải dùng đến máy tính, sẽ nhanh gọn hơn.
  • Viết chương trình đọc mã lỗi và xóa lỗi. (cái này minh chưa làm, nhưng nguyên lý cơ bản giống đọc thông tin data của xe).
  • Kết hợp với labview để đọc thông tin và vẽ dưới dạng đồ thị thì nhìn sẽ rất pro. Labview là một phần mềm thu thập thông tin / điều khiển, lập trình bằng giao diện đồ họa rất mạnh, giao diện rất đẹp. cần thêm thông tin các bạn có thể tham khảo trên internet.
  • Luu ý cho các bạn muốn phát triển ứng dụng mạnh, cần tốc độ xử lý cao, bộ nhớ trong lớn thì các bạn sinh viên nên học ARM. Đây là vi điều khiển 32 bit tốc độ xử lý cao, bộ nhớ trong lớn, có sẵn nhiều giao thức giao tiếp on chip SPI, I2C, CAN… sẽ đáp ứng được tât cả các yêu cầu của các bạn.
 

pearl

Điều Hành Viên
Thành viên BQT
link hay cho các anh em tìm hiểu.
 
Top