IoT4Car: Tangkap & Analisis Telemetri Kendaraan dengan Arduino MKR1000
Komponen dan Perlengkapan
Arduino MKR1000 — 1 unit
SparkFun Logic Level Converter - Bi-Directional — 1 unit
SparkFun OBD-II UART — 1 unit
SparkFun OBD-II to DB9 Cable — 1 unit
Aplikasi dan Layanan Online
Arduino IDE
freeboard
dweet
Tentang Proyek Ini
Latar Belakang
Saat mengemudi kendaraan Anda, melirik ke papan instrumen, apakah Anda pernah memikirkan untuk mengumpulkan pembacaan meter dan melakukan beberapa analisis? Data ini mungkin mengandung harta karun tersembunyi. Untuk individu, ini dapat mencerminkan kebiasaan mengemudi Anda, dapat memberi tahu kecepatan Anda, rata-rata mpg Anda, berapa banyak lampu lalu lintas yang Anda lewati, dan waktu tunggu Anda di setiap persimpangan. Untuk perusahaan, data ini sangat penting untuk pemantauan real-time dalam manajemen armada. Kondisi kendaraan, distribusi beban kerja, efisiensi bensin, dan bahkan lokasi kendaraan dapat semua dikirim kembali ke sistem kontrol pusat melalui cloud. Perusahaan dapat menggunakan pembelajaran mesin untuk memasukkan data ke dalam model pelatihan untuk memprediksi biaya dan bahkan menganalisis karakteristik pengemudi. Seiring dengan perkembangan IoT yang luas, aplikasi di atas tidak akan terlalu jauh. Dengan papan Arduino MKR, yang menargetkan aplikasi IoT, Anda dapat membuat perangkat yang berbicara dengan mobil Anda dan mengunggah data telemetri ke cloud sendirian. Tidak keren?
Berbicara dengan kendaraan
Kami memerlukan antarmuka untuk mengakses sistem kendaraan. Di mana kita bisa menembus mobil? Jawabnya adalah antarmuka OBD-II.
Apa itu OBD-II?
On-Board Diagnostics (OBD) adalah sistem diagnosa sendiri bawaan kendaraan, yang melalui kita dapat berkomunikasi dengan mobil kita. Ini pertama kali diperkenalkan di Amerika Serikat pada tahun 1994, dan menjadi syarat pada semua kendaraan AS tahun 1996 dan yang lebih baru. Negara lain, termasuk Kanada, bagian Uni Eropa, Jepang, Australia, dan Brasil mengadopsi legislasi serupa. OBD-II (generasi kedua) memiliki lima protokol sinyal, dan Controller Area Network (CAN bus) adalah salah satunya. CAN bus wajib diterapkan di semua mobil AS sejak 2008. Ada pengantar yang bagus tentang OBDII yang disediakan oleh CSS Electronics di Youtube. Dalam proyek ini, kami akan mengakses data melalui antarmuka OBD-II 16 pin.
Kontroler saya
Arduino adalah platform yang luar biasa untuk hobi, pembuat, dan profesional. Ia memiliki berbagai papan yang menargetkan aplikasi yang berbeda. Di sini saya menggunakan papan Arduino MKR WiFi 1000 karena kemampuan WiFi-nya. Anda juga dapat menggunakan papan lain yang Anda sukai. Saya akan merekomendasikan Arduino MKR GSM 1400, hanya karena GSM mencakup area yang jauh lebih luas daripada WiFi. Tetapi jangan khawatir, bahkan dengan WiFi kita dapat memiliki akses internet di sepanjang jalan. Saya akan menunjukkan solusi alternatif.
Papan penerjemah
Meskipun Arduino itu sendiri memiliki banyak I/O dan berbagai perpustakaan, kita masih membutuhkan papan yang dapat menterjemahkan protokol OBD ke dalam bahasa yang dapat dikenali oleh Arduino. Papan yang saya gunakan adalah SparkFun OBD-II UART Board.
Papan ini memungkinkan Anda untuk berinteraksi dengan bus OBD-II mobil Anda. Ini memberikan Anda antarmuka serial menggunakan himpunan perintah ELM327 dan mendukung semua standar OBD-II utama seperti CAN. Papan ini berisi chip STN1110, yang merupakan penerjemah OBD ke UART yang dapat digunakan untuk mengonversi pesan antara semua protokol OBD-II yang saat ini digunakan, dan UART.
Namun, harus diperhatikan bahwa papan penerjemah ini memiliki tegangan I/O 5 V, yang dapat merusak I/O papan Arduino MKR jika menghubungkannya secara langsung. Arduino MKR WiFI 1000 berjalan pada tegangan yang lebih rendah dan tegangan I/O-nya adalah 3.3 V. Oleh karena itu, diperlukan level shifter untuk mengonversi sinyal dari 5 V ke 3.3 V dan sebaliknya. Di bawah ini adalah gambar level shift yang saya gunakan.
Hubungkan rangkaian
Menghubungkan rangkaian cukup mudah. Cukup hubungkan pin 13 Rx dan pin 14 Tx Arduino MRK ke pin Tx dan Rx papan OBD-II UART melalui level shifter. Tentu saja, Anda perlu menghubungkan ground kedua papan bersama.
Untuk memudahkan debugging dan demonstrasi, saya juga menghubungkan layar LCD 1602 ke Arduino untuk menampilkan data secara real-time. Penyatuan kabel LCD ke Arduino dapat ditemukan di tutorial ini, dan oleh karena itu tidak akan dijelaskan lebih lanjut.
Di bawah ini adalah gambar koneksi breadboard. Garis hijau adalah untuk kabel yang menghubungkan Arduino dan papan OBD-II UART, sementara garis kuning adalah untuk kabel yang menghubungkan Arduino dan LCD. Skematis juga tersedia dalam lampiran.
Koneksi sebenarnya sedikit acak karena area breadboard yang terbatas, tetapi mengikuti skematis di atas. Saya menyertakan kabel micro USB dan ODB-II to DB9 dalam gambar.
Serial1 bukan Serial
Baiklah, saatnya untuk memprogram papan Arduino MKR kita. Karena papan Arduino MKR saya berbicara dengan papan penerjemah melalui UART, tidak perlu menginstal perpustakaan pihak ketiga. Mengirim perintah ke papan penerjemah hanya seperti berkomunikasi dengan Serial Monitor. Satu hal yang ingin saya tekankan adalah bahwa port serial yang terkait dengan Pin 13 dan Pin 14 adalah Serial 1! Port serial papan Arduino MKR merujuk ke port USBnya yang digunakan untuk berkomunikasi dengan komputer Anda. Jangan lupa untuk menginisialisasi port Serial 1 di dalam fungsi setup().
Serial1.begin(9600);
Dan gunakan Serial 1 untuk mendorong perintah ke papan penerjemah.
Serial1.println(message);
Pesan
Seperti yang Anda lihat, saya menggunakan variabel "message" untuk menyimpan perintah. Perintah OBD terdiri dari kode heksadesimal yang ditulis dalam karakter ASCII. Dua bilangan heksadesimal pertama merujuk ke mode layanan yang akan digunakan. Ada 10 layanan diagnosa yang dijelaskan dalam standar OBD-II terbaru SAE J1979. Karena kita tertarik pada pemantauan real-time, kami hanya akan menggunakan kode 01 untuk menampilkan data saat ini dalam proyek ini.
Setiap angka heksadesimal setelah mode layanan mewakili Parameter ID (PID) untuk mencapai fungsi khusus. Di bawah ini adalah tangkapan layar PID dalam mode layanan 01. Informasi lebih lanjut dapat ditemukan di Wikipedia.
Dalam proyek ini, saya akan menunjukkan cara mendapatkan kecepatan mobil, RPM mesin, tingkat bahan bakar, dan suhu pendingin mesin. Perintah OBD untuk keempat fungsi ini adalah:
010D // kecepatan mobil
010C // RPM mesin
012F // tingkat bahan bakar
0105 // suhu pendingin mesin.
Dekode data
Setelah perintah dikirim, papan Arduino MKR akan mendengarkan port Serial 1 untuk setiap respons. Lebih baik memberikan penundaan 200 ms setelah mengirim perintah. Saya menggunakan kode berikut untuk menerima respons.
void getResponse(void){ while(Serial1.available() > 0) { // Start by checking if we've received the end of message character ('\r'). if(Serial1.peek() == '\r'){ // reach the end of the message, clear the Serial buffer inChar = Serial1.read(); rxData[rxIndex] = '\0'; // Reset the buffer index so that the next character goes back at the beginning of the string rxIndex = 0; } // If we didnt get the end of the message character, just add the new character to the string else{ // Get the new character from the Serial port: inChar = Serial1.read(); // add the new character to the string, and increase the index variable: rxData[rxIndex++] = inChar; } } }
Respons dari papan penerjemah mengikuti format
">1 Repeated PIDs Data"
Misalnya, dalam tangkapan layar di atas, saya mengirim "010D" untuk mendapatkan kecepatan mobil. Responsnya adalah ">1 0D 00". 5 karakter pertama menunjukkan bahwa mobil menerima perintah dan mengulang PID 0x0D kembali. Dua digit terakhir mengembalikan data kecepatan 0x00.
Kemudian saya mengirim "010C" untuk mendapatkan RPM mesin, respons ">1 0C" menunjukkan pengakuan perintah, data 0x098C adalah 4 kali nilai RPM mesin dalam heksadesimal. 0x098C / 4 = 611 des, jadi RPM mesin adalah 611 rpm.
Setelah itu, saya mengirim perintah "012F" untuk mendapatkan tingkat bahan bakar, dan saya mendapatkan data 0x1D. Tingkat bahan bakar dihitung sebagai 0x1D / 255 * 100 = 11% des.
Perintah terakhir adalah "0105", yang memberikan suhu pendingin mesin 0x79. Suhu sebenarnya adalah 0x79 - 40 = 81 derajat C des. Kemudian urutan perintah berulang.
Seperti yang Anda lihat, baris respons memiliki spasi antara dua digit heksadesimal, dan 5 digit pertama hanyalah mengulang perintah. Oleh karena itu, data sebenarnya dimulai dari karakter ke-6 (pertama dimulai dari indeks 0).
Dalam pemrograman dan debugging, serial monitor berguna, tetapi ketika berbicara tentang aplikasi nyata, layar LCD lebih portable dan memenuhi persyaratan daya IoT. Cukup ganti serial monitor dengan layar LCD, Anda dapat memantau data mobil Anda secara real-time. Di bawah ini adalah foto penggunaan proyek ini di mobil saya sendiri.
Cloud our data
Keunggulan Arduino MKR dibandingkan UNO adalah aksesibilitas internetnya. Menargetkan aplikasi IoT, Arduino MKR akan membuat industri menjadi lebih cerdas dan terhubung. Dalam aplikasi otomotif, MKR WiFi 1000 mungkin bukan papan terbaik karena sinyal WiFi jarang di lingkungan luar, tetapi saya menggunakan ponsel saya sebagai hotspot pribadi, jadi tidak masalah.
Ada banyak platform cloud lainnya untuk menyimpan, melihat, dan memproses data. Anda dapat memilih yang Anda sukai. Saya akan menggunakan dweet.io dan freeboard.io sebagai contoh. Dweet.io menyediakan API yang dapat Anda kirim data ke sana. Freeboard.io memiliki handle untuk mengambil data dweet.io dan memvisualisasikannya. Ada beberapa tutorial untuk menyiapkan dweet.io dan freebboard.io, jadi saya tidak akan menjelaskan lagi. Jika Anda tertarik, berikut beberapa contoh, contoh 1, contoh 2.
Kode pengiriman data ditampilkan di bawah sebagai ilustrasi cara membuat perintah dweet.
void httpRequest() { client.stop(); // create data string to send to freeboard if (client.connect(server, 80)){ Serial.println("Connected"); String data = "POST /dweet/for/mkr1000?RPM="; data.concat(vRPM); // upload engine RPM data.concat("&Speed="); data.concat(vSpeed); // upload car speed data.concat("&Fuel="); data.concat(vFuel); // upload fuel level data.concat("&Temp="); data.concat(vTemp); // upload coolant temperature client.println(data); client.println("Host: https://www.dweet.io"); client.println("Connection: close"); // end of connection client.println(); } else { lcd.clear(); lcd.setCursor(0,0); lcd.println("Connection failed"); } }
Di freeboard.io, kita perlu membuat dasbor baru, dan di dalam dasbor ini, membuat sumber data baru. Hubungkan sumber data ini ke thing dweet.io yang Anda definisikan dalam kode. Dalam kasus saya, itu adalah mkr1000. Kemudian buat widget Gauge baru yang akan kita gunakan untuk menampilkan data. Beri nama, dan hubungkan ke salah satu variabel kita. Di bawah ini adalah tangkapan layar dasbor saya. Ini menunjukkan SPEED, RPM, FUEL LEVEL, dan COOLANT TEMPERATURE.
Kesimpulan
Saya mencoba papan di mobil saya sendiri, dan berfungsi dengan baik. Saya sedang merancang PCB yang menyertakan semua fungsi dalam rangka terintegrasi. Harapnya, saya akan menulis lebih banyak tutorial di masa depan. Saya mungkin akan menyertakan demo video juga. Maaf kali ini, saya tidak dapat mengambil video sekaligus mengemudi mobil saya! Dan Anda juga harus berhati-hati saat melakukan debugging kode saat mengemudi di jalan!
Papan Arduino MKR WiFi cukup baik untuk aplikasi ini. Jika saya memiliki lebih banyak papan, saya pikir saya bisa mencoba papan MKR GSM 1400. Silakan gunakan papan IoT lain dengan tutorial ini dan beri saya masukan Anda.
Bekerja pada proyek ini menyenangkan dan edukatif. Saya menikmati perasaan saat memecahkan masalah. Ini juga adalah kegembiraan saya untuk membagikan apa yang saya tahu di web. Terima kasih telah membaca. Beritahu saya jika Anda memiliki pertanyaan atau komentar.
Kode
IoT4Car_code
IoT4Car_codeC/C++
Program ini akan berbicara dengan kendaraan menggunakan papan OBDII-UART, dan menampilkan hasilnya di LCD, dan mengunggah ke platform IoT freeboard
/* * OBDII-UART-Serial version 9 * This program will talk to vehicle using the OBDII-UART board, * and display the results on the LCD, and upload to freeboard IoT platform * * Author: zhaoshentech * Updated: 2018-08-27 * * updates: * v3: modified the getResponse() function so that the buffer receives the correct response. * add the getRPM() to get the engine RPM from the vehicle. * v4: add the getSpeed() function to get the speed of the vehicle * v5: add the LCD module and display the speed and RPM on the LCD * v6: is the wifi version * v7: is the non-wifi, non-serial version. Remove serial initialization, * so that the board can work without a computer. * v8: is the non-wifi, non-serial version. Add fuel level and coolant temperature. * rearrange the display location. * v9: is the wifi, non-serial version. Upolad speed, RPM, fuel level and coolant temperture * * LCD circuit connection: * LCD RS pin to digitial pin 12 * LCD Enable pin to digital pin 11 * LCD D4 pin to digital pin 5 * LCD D5 pin to digital pin 4 * LCD D6 pin to digital pin 3 * LCD D7 pin to digital pin 2 * LCD R/W pin to ground * 10 K potentialmeter: * ends to +5V and ground * wiper to LCD VO pin (pin 3) */ //////////////////////////////////////////////////////// // // WiFi related // /////////////////////////////////////////////////////// #include<SPI.h> #include<WiFi101.h> char ssid[] = "YOUR WIFI SSID"; // wifi ID char pass[] = "YOUR WIFI PSWD"; // wifi password char server[] = "www.dweet.io"; // freeboard and dweet Settings unsigned long lastConnectionTime = 0; // track the last connection time const unsigned long postingInterval = 10L * 1000L; // post data every 10 seconds WiFiClient client; //Initialize the wifi client int status = WL_IDLE_STATUS; // the WiFi radio status // include the LDC libaray #include <LiquidCrystal.h> const int rs = 12, en = 11, d4 =5, d5 =4, d6 = 3, d7 = 2; LiquidCrystal lcd(rs, en, d4, d5, d6, d7); // This is a character buffer that will store the data from the serial port: char rxData[20]; char rxIndex = 0; char inChar = 0; String message; // Variables to hold the speed and the RPM data: int vSpeed = 0; int vRPM = 0; int vFuel = 0; int vTemp = 0; void setup() { // Set up the LCD's number of columns and rows: lcd.begin(16,2); lcd.clear(); // check the presence of the shield: if (WiFi.status() == WL_NO_SHIELD) { lcd.println("WiFi not ready"); while(true); } // attempt to connect to WiFi network: while (status != WL_CONNECTED) { lcd.clear(); lcd.setCursor(0, 0); lcd.println("Connecting WiFi..."); status = WiFi.begin(ssid, pass); // wait for 5 second for the connection: delay(5000); } lcd.setCursor(0, 1); lcd.println("Connected!"); // Serial1 is the acutal port to talk to vehicle Serial1.begin(9600); resetBuffer(); } void loop() { while ( status != WL_CONNECTED) { lcd.clear(); lcd.setCursor(0,0); // Connect to WPA/WPA2 Wi-Fi network Serial.println("Connecting to Wifi"); lcd.println("Connect WiFi..."); status = WiFi.begin(ssid, pass); // wait 10 seconds for connection delay(5000); } getSpeed(); getRPM(); getFuel(); getCoolTemp(); if (millis() - lastConnectionTime > postingInterval) { httpRequest(); lastConnectionTime = millis(); } } // getRPM data sends the "010C" command to the Serial1 port // and call the getResponse() to collect the data. Then it prints // the RPM data on the Serial Monitor. void getRPM(void){ message = "010C"; Serial1.println(message); delay(200); //clear the current line for (int i = 8; i < 16; ++i) { lcd.setCursor(i, 0); // 0 row, i column lcd.write(' '); } lcd.setCursor(8,0); // first row second half in the LCD screen //wait reponse getResponse(); // The RPM response divided by 4 gives the correct value. vRPM = ((strtol(&rxData[6],0,16)*256) + strtol(&rxData[9],0,16))/4; lcd.print(vRPM); lcd.print(" rpm"); } void getSpeed(void){ message = "010D"; Serial1.println(message); delay(200); //clear the current line: for (int i = 0; i < 8; ++i) { lcd.setCursor(i, 0); // 0 row, i column lcd.write(' '); } lcd.setCursor(0,0);// first row first half in the LCD screen //wait for the response from the car getResponse(); vSpeed = strtol(&rxData[6], 0, 16); // in the unit of km/h vSpeed = vSpeed * 0.621371; // in the unit of mph lcd.print(vSpeed); lcd.print(" mph"); } void getFuel(void){ message = "012F"; Serial1.println(message); delay(200); // clear the current line: for (int i = 0; i < 8; i++){ lcd.setCursor(i, 1); // 1st row, i column lcd.write(' '); } lcd.setCursor(0, 1); // second row first half in the LCD screen //wait for the response from the car getResponse(); vFuel = strtol(&rxData[6], 0, 16); // in the scale of 255 //vFuel = 244; // debug usage vFuel = 1.0* vFuel / 255 *100; // in the scale of 100 lcd.print(vFuel); lcd.print(" %"); //Serial.println(vFuel); // debug usage } void getCoolTemp(void){ message = "0105"; Serial1.println(message); delay(200); // clear the current line: for (int i = 8; i < 16; i++){ lcd.setCursor(i, 1); // 1st row, i column lcd.write(' '); } lcd.setCursor(8, 1); // second row second half in the LCD screen //wait for the response from the car getResponse(); vTemp = strtol(&rxData[6], 0, 16); // in the unit of C but offset by 40 degrees vTemp = vTemp - 40; // offset by 0 lcd.print(vTemp); // print the degree C lcd.write(0xDF); lcd.print("C"); } // The getResponse function collects incoming data from the UART into the rxData buffer // and exits when the response is transferred. Once the carriage return string // is detected, the rxData buffer is null terminated (so that we can treat it as a string) // and the rxData index is reset to 0 so that the next string can be copied. void getResponse(void){ while(Serial1.available() > 0) { // Start by checking if we've received the end of message character ('\r'). if(Serial1.peek() == '\r'){ // reach the end of the message, clear the Serial buffer inChar = Serial1.read(); rxData[rxIndex] = '\0'; // Reset the buffer index so that the next character goes back at the beginning of the string rxIndex = 0; } // If we didnt get the end of the message character, just add the new character to the string else{ // Get the new character from the Serial port: inChar = Serial1.read(); // add the new character to the string, and increase the index variable: rxData[rxIndex++] = inChar; } } } void resetBuffer(void){ for (int i = 0; i < 20; i++){ rxData[i] = 0; } } void httpRequest() { client.stop(); // create data string to send to freeboard if (client.connect(server, 80)){ Serial.println("Connected"); String data = "POST /dweet/for/mkr1000?RPM="; data.concat(vRPM); // upload engine RPM data.concat("&Speed="); data.concat(vSpeed); // upload car speed data.concat("&Fuel="); data.concat(vFuel); // upload fuel level data.concat("&Temp="); data.concat(vTemp); // upload coolant temperature client.println(data); client.println("Host: https://www.dweet.io"); client.println("Connection: close"); // end of connection client.println(); } else { lcd.clear(); lcd.setCursor(0,0); lcd.println("Connection failed"); } }