HUD Mobil: Tampilan Kecepatan & Kompas Kaca Depan Real-Time
Komponen dan Perlengkapan
Arduino Nano R3
1
Tentang Proyek Ini
Deskripsi:
Saya menginginkannya! Saya iri dengan mobil besar milik teman saya Taggi yang menampilkan kecepatan di kaca depan. Saya harus memilikinya, saya harus membuatnya sendiri tentunya!
Sudut pandang perangkat lunak:
Merancang rangkaian bagian paling sulit dan membingungkan adalah koneksi dari tampilan 7 segmen ke dekoder BCD karena angka-angka adalah "terbalik" (cermin).
Ada 3 tombol: + dan - untuk menambah/menurunkan kecerahan dan S/H untuk beralih antara Kecepatan dalam km/h dan Arah dalam derajat; dalam kasus Arah juga LED merah menyala, artinya "derajat" kompas (1-360°) saat bergerak. Ini bukan kompas elektronik, itu GPS dan Anda perlu bergerak untuk mendapatkan informasi yang benar tentang arah Anda bergerak. Kecerahan disimpan di memori EEPROM setelah satu menit. Kecerahan diubah baik pada tampilan maupun pada LED melalui pin PWM.
Bagian penting dari kode adalah untuk mengumpulkan data dari GPS, terutama kecepatan dan arah, dengan mengambilnya dari setiap kalimat NMEA. Bahkan menggunakan kelas String, terutama untuk manipulasi kalimat NMEA Serial, alur pemrosesan secara keseluruhan stabil dan kokoh; ia menggunakan "serialEvent()" untuk menerima data dari GPS satu kali per detik, kemudian memanggil "nmeaExtractData()" dan akhirnya memeriksa paket data dengan "nmea0183_checksum()" untuk memastikan integritas data. Jika Anda menggunakan GPS merk dan model lain pastikan kalimatnya memiliki struktur yang sama atau Anda harus melakukan beberapa perubahan di sini. Misalnya EM406A menggunakan id paket "$GPRMC", BT220 menggunakan "$GNRMC" sebagai gantinya... hanya perubahan nama kecil... Tautan yang berguna dapat membantu Anda dengan tes checksum: https://nmeachecksum.eqth.net - Berikut contoh kalimat NMEA lengkap, berisi: id, waktu, validitas, lintang, bujur, kecepatan, koers sejati, tanggal, variasi dan checksum.
$GNRMC, 095836.000, A, 4551.9676, N, 01328.7118, E, 2.09, 341.84, 280519,, *08
Skets menyediakan untuk mengaktifkan latch untuk setiap dekoder BCD tampilan tunggal satu per satu, menetapkan kode angka pada bus biner 4 bit, menonaktifkan latch, dan seterusnya saat nilai berubah. Sisi kiri nol tidak signifikan dihilangkan (tidak ditampilkan).
Sebelum memuat skets barukeMCUingatlah untuk melepas Jumper: itu terhubung ke pin Rx Arduino dan saat pemuatan itu tentu saja dalam konflik dengan Tx GPS. Setelah pemuatan perangkat lunak letakkan kembali Jumper untuk memulihkan fungsionalitas normal.
Daftar komponen:
1 x MCU Arduino Nano
3 x tampilan 7 segmen 5161as, katoda umum, merah
1 x GPS serial TTL Beitian BN-220 (kalimat GNRMC 1 Hz)
1 x Jumper
3 x tombol (normal terbuka) + 3 x cap
22 x resistor 1/4W 220 ohm
1 x LED 3mm, merah
2 x kapasitor 100n
3 x dekoder BCD 14511 + latch
1 x USB "B" female
1 x garis strip 2x pin pria (untuk Jumper)
1 x garis strip 4x pin pria untuk dibengkokkan 90° (untuk GPS)
1 x kabel pigtail menghubungkan GPS ke 4 pin papan
22 x pin pria garis strip untuk menggabungkan dua PCB dalam tumpukan
1 x lem dua sisi untuk menempelkan GPS pada PCB
6 x garis strip 5x pin female yang diputar (untuk tampilan)
50 x rebus tembaga 0.6mm
8 x baut M3
4 x menara female M3 tinggian 20mm
1 x kotak plastik + tutup (lihat file 3D saya siap dicetak, di bawah)
PCB (printed circuit boards):
Saya menggunakan dua PCB dua muka untuk alasan itu sekitar 50 rebus melalui atau pin digunakan untuk menyelesaikan rute untuk seluruh rangkaian. Ada juga 5 lubang penyelarasan yang harus dibuat terlebih dahulu. Saya merancang di setiap PCB 5 titik penyelarasan ini. Di bagian unduhan Anda memiliki semua file PCB, komponen dan wajah solder, tercermin, untuk diunduh dan dicetak melalui printer laser pada lembaran "kuning" atau "biru"; kali ini saya menggunakan yang biru tetapi yang kuning juga bagus dan harganya lebih murah. Saya harus berkata lembaran biru lebih baik... Saat mencetak ingatlah untuk menonaktifkan pengaturan hemat toner, gunakan resolusi 1200 dpi untuk hasil hitam nyata yang dalam. Proses transfer toner dari lembar sihir ke PCB dilakukan dengan penggunaan setrika panas... Di sekitar Internet ada beberapa tutorial yang menunjukkan bagaimana menghasilkan PCB yang bagus tetapi ingat poin-poin penting ini: bersihkan sempurna dan sedikit gerus tembaga dengan spons dapur, 5 menit setrika, kejutan termal air, dua wajah penyelarasan melalui 5 lubang (saya menggunakan 5 pin pada permukaan LED putih besar untuk melihat lubang), lindungi wajah lain selama proses korosi. Mencetak juga wajah komponen membuat proyek ini "profesional" :-)
CATATAN:foto diambil sebelum versi terakhir, ini berarti beberapa detail dapat berbeda: misalnya kabel hijar aneh di sekitar PCB dalam versi akhir hilang, atau Jumper telah pindah dari PCB #1 ke PCB #2. Anda harus menyiapkan GPS pada kecepatan 9600 bps, hanya kalimat NMEA GNRMC 1 Hz; ini dapat dilakukan menggunakan perangkat lunak pengaturan miliknya. Terakhir, sebelum menyolder konektor USB female, tempelkan potongan kecil isolasi di bawahnya untuk menghindari kontak yang tidak diinginkan dengan garis tembaga, di sisi komponen, tepat di bawahnya.
Memberikan daya untuk pertama kalinya Anda akan menemukan angka-angka "tidak dapat dibaca", karena itu dibuat untuk dilihat melalui refleksi di kaca depan (cermin). Pasang di mobil di depan kursi pengemudi, temukan tempat yang nyaman dan pasang. Saya menyarankan untuk menghubungkan kabel daya USB setelah posisi kontak sehingga akan menyala / mati ketika mesin menyala / mati. Itu saja para wanita dan pria!
Berita dan Perbaikan:
25.03.2021: untuk menyelesaikan proyek saya menyiapkan dua model .STL 3D di tautan ini: https://grabcad.com/library/car-hud-1 tempat Anda dapat mengunduh file mengenai kotak yang dibentuk dan tutup panelnya untuk proyek ini untuk dicetak melalui printer 3D.
20.07.2021: tersedia V2 (versi dua) dari perangkat lunak yang bersama dengan dua kabel dan satu resistor Anda akan memiliki informasi ketinggian di tampilan sebagai ribuan meter (mis. 0.89 = 890m), cukup tekan tombol S/H lagi! Instruksi: 1) setup GPS untuk mengeluarkan juga kalimat NMEA $GNGGA melalui perangkat lunak miliknya; 2) potong koneksi aktual antara pin PWM d5 Arduino dan sisa rangkaian, hubungkan ke pin titik desimal tampilan tengah dengan resistor 220ohm; 3) hubungkan pin digital d13 Arduino ke rangkaian tempat d5 sebelumnya terhubung; lihat beberapa foto di bawah ini; 4) instal skets V2 pada Arduino Nano Anda.
01.11.2021: versi V2.2 siap untuk Anda. Ada yang baru? Tentang ketinggian (sudah hadir dengan V2) HUD menampilkan nilai penuh hingga 999m, mulai dari 1000m itu menampilkan nilai dalam ribuan, mis. 1.24 (1240m) atau 2.02 (2020m). Saya menambahkan sensor LDR GL5539, resistor 10kOhm dan 2 kabel. Lihat foto di bawah ini untuk modifikasi V2.2 dan instal skets V2.2 pada Arduino Anda. Sensor LDR akan mengubah kecerahan secara otomatis (3 tingkat) tergantung pada tingkat cahaya (hari, berawan atau malam). Tombol (+) dan (-) masih bekerja untuk mengubah kecerahan secara manual, mereka memiliki prioritas; untuk kembali ke otomatis tekan tombol (+) dan (-) bersama-sama untuk sementara. Memori EEPROM internal untuk menyimpan tingkat kecerahan tidak lagi digunakan. Lakukan modifikasi V2 terlebih dahulu!
Saat berkendara, hanya berkendara. Berkendara dengan aman!
Kode
Skets Arduino Car-HUD
/* Skets ini bertindak sebagai HUD kaca depan mobil (head up display), oleh Marco Zonca, 10/2020 Arduino Nano sebagai CPU, GPS BT-220 nmea setiap 1 detik, 3 x tombol, 3 x tampilan 7 segmen katoda umum, 3 x dekoder latch BCD 14511, memori EEPROM MPU (1 byte) dan banyak resistor; PERINGATAN: ======= Sebelum memperbarui perangkat lunak lepaskan pin Rx pada Arduino (TX dari GPS) melalui JUMPER */ #include <Wire.h> #include <EEPROM.h> String inputString = ""; String nm_time = "00:00:00"; String nm_validity = "V"; String nm_latitude = "dd°mm.mmmm'N"; String nm_longitude = "ddd°mm.mmmm'E"; String nm_knots = "0.0kn"; float nmf_knots = 0.0; float nmf_kmh = 0.0; int nmi_kmh = 0; String nm_truecourse = "360"; float nmf_truecourse = 360; String nm_date = "dd/mm/yyyy"; int nmi_truecourse = 0; byte kCent = 0; byte kDeci = 0; byte kUnit = 0; byte tCent = 0; byte tDeci = 0; byte tUnit = 0; byte brightness = 120; byte latch_off = HIGH; byte latch_on = LOW; int n=0; unsigned long lastmemcheck = 0; unsigned long memcheck = 60000; // check to save "brightness" value in EEPROM every 60 seconds bool stringComplete = false; bool isKMH=true; bool ret = false; const int disp001 = 2; // units display latch const int disp010 = 8; // tens display latch const int disp100 = 12; // hundreds display latch const int disp001dim = 9; // units display dimmer/off pin const int disp010dim = 10; // tens display dimmer/off pin const int disp100dim = 11; // hundreds display dimmer/off pin const int button_kt = 14; // kmh/truecourse button const int button_more = 15; // brightness + button const int button_less = 16; // brightness - button const int degreesLED = 3; // degrees LED const int bit_3 = 7; // bit 3 const int bit_2 = 6; // bit 2 const int bit_1 = 5; // bit 1 const int bit_0 = 4; // bit 0 const int dly = 10; // delay latch m/sec const byte off = 0; // the same as brightness=0 const int addr = 0; // EEPROM address for brightness value const byte numbers[10] [4] = {{0,0,0,0},{1,0,0,0},{0,1,0,0},{1,1,0,0},{0,0,1,0}, {1,0,1,0},{0,1,1,0},{1,1,1,0},{0,0,0,1},{1,0,0,1}}; // bits 0,1,2,3 void setup() { Serial.begin(9600); Wire.begin(); inputString.reserve(200); brightness = EEPROM.read(addr); if (brightness > 250 || brightness < 10) { brightness=120; } // avoid crazy values from 1st EEPROM read pinMode(disp001, OUTPUT); pinMode(disp010, OUTPUT); pinMode(disp100, OUTPUT); pinMode(degreesLED, OUTPUT); pinMode(button_kt, INPUT_PULLUP); pinMode(button_less, INPUT_PULLUP); pinMode(button_more, INPUT_PULLUP); pinMode(bit_3, OUTPUT); pinMode(bit_2, OUTPUT); pinMode(bit_1, OUTPUT); pinMode(bit_0, OUTPUT); analogWrite(disp001dim, off); // off and zero displays analogWrite(disp010dim, off); analogWrite(disp100dim, off); analogWrite(degreesLED, off); setBusNr(0); digitalWrite(disp001, latch_on); digitalWrite(disp010, latch_on); digitalWrite(disp100, latch_on); delay(dly); digitalWrite(disp001, latch_off); digitalWrite(disp010, latch_off); digitalWrite(disp100, latch_off); analogWrite(disp001dim, brightness); // on display analogWrite(disp010dim, brightness); analogWrite(disp100dim, brightness); } // setup() void loop() { // GPS NMEA ------------------ if (stringComplete == true) { // received nmea sentence by serial port RX ret = nmeaExtractData(); inputString = ""; stringComplete = false; if (ret == true) { kCent=nmi_kmh/100; n=nmi_kmh-(kCent*100); kDeci=n/10; n=nmi_kmh-(kCent*100)-(kDeci*10); kUnit=n; tCent=nmi_truecourse/100; n=nmi_truecourse-(tCent*100); tDeci=n/10; n=nmi_truecourse-(tCent*100)-(tDeci*10); tUnit=n; display(); } } if (millis() > (lastmemcheck+memcheck)) { // put in memory brightness value (if modified) EEPROM.update(addr,brightness); lastmemcheck=millis(); } checkButtons(); } void display() { if (isKMH == true) { // speed in km/h (isKMH=true) analogWrite(degreesLED, off); setBusNr(kUnit); digitalWrite(disp001, latch_on); delay(dly); digitalWrite(disp001, latch_off); if (kDeci > 0 || kCent > 0) { // set tens off if tens=0 (and also hundreds=0) setBusNr(kDeci); digitalWrite(disp010, latch_on); delay(dly); digitalWrite(disp010, latch_off); analogWrite(disp010dim, brightness); } else { analogWrite(disp010dim, off); } if (kCent > 0) { // set hundreds off if=0 setBusNr(kCent); digitalWrite(disp100, latch_on); delay(dly); digitalWrite(disp100, latch_off); analogWrite(disp100dim, brightness); } else { analogWrite(disp100dim, off); } } else { // true bearing in degrees (isKMH=false) analogWrite(degreesLED, brightness); setBusNr(tUnit); digitalWrite(disp001, latch_on); delay(dly); digitalWrite(disp001, latch_off); if (tDeci > 0 || tCent > 0) { // set tens off if tens=0 (and also hundreds=0) setBusNr(tDeci); digitalWrite(disp010, latch_on); delay(dly); digitalWrite(disp010, latch_off); analogWrite(disp010dim, brightness); } else { analogWrite(disp010dim, off); } if (tCent > 0) { // set hundreds off if=0 setBusNr(tCent); digitalWrite(disp100, latch_on); delay(dly); digitalWrite(disp100, latch_off); analogWrite(disp100dim, brightness); } else { analogWrite(disp100dim, off); } } } // display() void checkButtons(){ if (digitalRead(button_kt) == LOW) { if (isKMH == true) { isKMH=false; } else { isKMH=true; } delay(250); } if (digitalRead(button_more) == LOW) { if (brightness <= 240) { brightness=brightness+10; } analogWrite(disp001dim, brightness); analogWrite(disp010dim, brightness); analogWrite(disp100dim, brightness); delay(100); } if (digitalRead(button_less) == LOW) { if (brightness >= 20) { brightness=brightness-10; } analogWrite(disp001dim, brightness); analogWrite(disp010dim, brightness); analogWrite(disp100dim, brightness); delay(100); } } // checkButtons() void setBusNr(int number) { // sets 4 bits bus for (byte b=0; b<=3; b++) { if (numbers[number][b]==0) { if (b==0) {digitalWrite(bit_0, LOW);} if (b==1) {digitalWrite(bit_1, LOW);} if (b==2) {digitalWrite(bit_2, LOW);} if (b==3) {digitalWrite(bit_3, LOW);} } else { if (b==0) {digitalWrite(bit_0, HIGH);} if (b==1) {digitalWrite(bit_1, HIGH);} if (b==2) {digitalWrite(bit_2, HIGH);} if (b==3) {digitalWrite(bit_3, HIGH);} } } } // setBusNr() // extract data from nmea inputString bool nmeaExtractData() { int d=0; int s=0; int y=0; int z=0; float t=0; bool ret = false; //true if nmea sentence = $GNRMC and valid CHKSUM if ((inputString.substring(0,6) == "$GNRMC") && (inputString.substring(inputString.length()-4,inputString.length()-2) == nmea0183_checksum(inputString))) { y=0; for (s = 1; s < 11; s ++) { y=inputString.indexOf(",",y); switch (s) { case 1: //-----------------------time z=inputString.indexOf(",",y+1); if (z>(y+1)) { nm_time=inputString.substring(y+1,y+2+1)+":"+inputString.substring(y+1+2,y+4+1)+":"+inputString.substring(y+1+4,y+6+1); } y=z; break; case 2: //-----------------------validity z=inputString.indexOf(",",y+1); if (z>(y+1)) { nm_validity=inputString.substring(y+1,y+1+1); } y=z; break; case 3: //-----------------------latitude z=inputString.indexOf(",",y+1); if (z>(y+1)) { nm_latitude=inputString.substring(y+1,y+2+1)+"°"+inputString.substring(y+1+2,y+10+1)+"'"; } y=z; break; case 4: //-----------------------north/south z=inputString.indexOf(",",y+1); if (z>(y+1)) { nm_latitude=nm_latitude + inputString.substring(y+1,y+1+1); } y=z; break; case 5: //-----------------------longitude z=inputString.indexOf(",",y+1); if (z>(y+1)) { nm_longitude=inputString.substring(y+1,y+3+1)+"°"+inputString.substring(y+1+3,y+11+1)+"'"; } y=z; break; case 6: //-----------------------east/west z=inputString.indexOf(",",y+1); if (z>(y+1)) { nm_longitude=nm_longitude + inputString.substring(y+1,y+1+1); } y=z; break; case 7: //-----------------------speed knots z=inputString.indexOf(",",y+1); if (z>(y+1)) { nmf_knots=inputString.substring(y+1,z).toFloat(); t=roundOneDec(nmf_knots); nm_knots=String(t,1)+"kn"; nmf_kmh=roundTwoDec(nmf_knots * 1.852); nmi_kmh=roundZeroDec(nmf_knots * 1.852); } y=z; break; case 8: //-----------------------true course z=inputString.indexOf(",",y+1); if (z>(y+1)) { nmf_truecourse=inputString.substring(y+1,z).toFloat(); t=roundZeroDec(nmf_truecourse); nmi_truecourse=t; d=t; nm_truecourse=d; } y=z; break; case 9: //-----------------------date z=inputString.indexOf(",",y+1); if (z>(y+1)) { nm_date=inputString.substring(y+1,y+2+1)+"/"+inputString.substring(y+1+2,y+4+1)+"/20"+inputString.substring(y+1+4,y+6+1); } y=z; break; case 10: // statements n.u. break; default: // statements n.u. break; } } ret=true; } return ret; } // nmeaExtractData() /* SerialEvent occurs whenever a new data comes in the hardware serial RX. This routine is run between each time loop() runs, so using delay inside loop can delay response. Multiple bytes of data may be available. */ void serialEvent() { while (Serial.available()) { char inChar = (char)Serial.read(); inputString += inChar; // if the incoming character is a newline, set a flag so the main loop can // do something about it if (inChar == '\n') { stringComplete = true; } } } // serialEvent() //calculate checksum of nmea sentence String nmea0183_checksum(String nmea_data) { int crc = 0; String chSumString = ""; int i; // ignore the first $ sign, checksum in sentence for (i = 1; i < (nmea_data.length()-5); i ++) { // remove the - 5 if no "*" + cksum + cr + lf are present crc ^= nmea_data[i]; } chSumString = String(crc,HEX); if (chSumString.length()==1) { chSumString="0"+chSumString.substring(0,1); } chSumString.toUpperCase(); return chSumString; } // nmea0183_checksum(String nmea_data) // round zero decimals float roundZeroDec(float f) { float y, d; y = f*1; d = y - (int)y; y = (float)(int)(f*1)/1; if (d >= 0.5) { y += 1; } else { if (d < -0.5) { y -= 1; } } return y; } // round one decimal float roundOneDec(float f) { float y, d; y = f*10; d = y - (int)y; y = (float)(int)(f*10)/10; if (d >= 0.5) { y += 0.1; } else { if (d < -0.5) { y -= 0.1; } } return y; } // round two decimals float roundTwoDec(float f) { float y, d; y = f*100; d = y - (int)y; y = (float)(int)(f*100)/100; if (d >= 0.5) { y += 0.01; } else { if (d < -0.5) { y -= 0.01; } } return y; }