├── youBoot.jpg ├── preview1.jpg ├── preview2.jpg ├── .gitattributes ├── ProMini-v2-OLED-Pi ├── TJA1050.jpg ├── TCP_SerPort_auto.apk ├── ProMini-v1-schematic.jpg ├── MCP2515-CAN-Bus-Interface.jpg ├── ATmega328P-PU-PIN-Diagram-connection-configration.jpg ├── CAN-Bus-Dummies-Simple-Message-Overview-SOF-RTR-CRC-ACK.png └── ProMini-v2-OLED-Pi.ino └── README.md /youBoot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helimania/Blackheart/HEAD/youBoot.jpg -------------------------------------------------------------------------------- /preview1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helimania/Blackheart/HEAD/preview1.jpg -------------------------------------------------------------------------------- /preview2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helimania/Blackheart/HEAD/preview2.jpg -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /ProMini-v2-OLED-Pi/TJA1050.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helimania/Blackheart/HEAD/ProMini-v2-OLED-Pi/TJA1050.jpg -------------------------------------------------------------------------------- /ProMini-v2-OLED-Pi/TCP_SerPort_auto.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helimania/Blackheart/HEAD/ProMini-v2-OLED-Pi/TCP_SerPort_auto.apk -------------------------------------------------------------------------------- /ProMini-v2-OLED-Pi/ProMini-v1-schematic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helimania/Blackheart/HEAD/ProMini-v2-OLED-Pi/ProMini-v1-schematic.jpg -------------------------------------------------------------------------------- /ProMini-v2-OLED-Pi/MCP2515-CAN-Bus-Interface.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helimania/Blackheart/HEAD/ProMini-v2-OLED-Pi/MCP2515-CAN-Bus-Interface.jpg -------------------------------------------------------------------------------- /ProMini-v2-OLED-Pi/ATmega328P-PU-PIN-Diagram-connection-configration.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helimania/Blackheart/HEAD/ProMini-v2-OLED-Pi/ATmega328P-PU-PIN-Diagram-connection-configration.jpg -------------------------------------------------------------------------------- /ProMini-v2-OLED-Pi/CAN-Bus-Dummies-Simple-Message-Overview-SOF-RTR-CRC-ACK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helimania/Blackheart/HEAD/ProMini-v2-OLED-Pi/CAN-Bus-Dummies-Simple-Message-Overview-SOF-RTR-CRC-ACK.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blackheart (PRIVATE) 2 | Digital instrument cluster 3 | 4 | With commercial offers, email me helimania@me.com or contact me on Drive2 https://www.drive2.ru/users/helimania/ 5 | 6 | [![Boot](https://github.com/helimania/Blackheart/blob/master/youBoot.jpg)](https://www.youtube.com/watch?v=Y-z6Gsg9BnU) 7 | 8 | ![preview 1](https://github.com/helimania/Blackheart/blob/master/preview1.jpg) 9 | 10 | Requires: 11 | - Raspberry PI Zero 12 | - Atmega328P with Arduino bootloader 13 | - Microchip MCP23017 port expander 14 | - 1.5inch OLED display Module (if needed) 15 | - Buildroot 2018.08.3 or higher 16 | - QT 5.11 17 | - А little time and patience 18 | 19 | # Schematic 20 | 21 | However, I did not redraw the scheme for this project, but I hope you will understand how it should look. 22 | 23 | ![schematic](https://github.com/helimania/Blackheart/blob/master/ProMini-v2-OLED-Pi/ProMini-v1-schematic.jpg) 24 | 25 | Arduino and Raspberry connected with only one wire TX (arduino) -> RX (raspberry) and scheme taken from here https://elinux.org/RPi_GPIO_Interface_Circuits 26 | 27 | For raspberry external PWM sound used scheme from here https://www.tinkernut.com/2017/04/adding-audio-output-raspberry-pi-zero-tinkernut-workbench/#lightbox[10497]/2/ 28 | 29 | Arduino sketch included. 30 | 31 | # Prepare QT 5.11 32 | 33 | Get raspbian images from here https://www.raspberrypi.org/downloads/raspbian/ and follow an official installation guide to boot it up. 34 | 35 | [RPI] 36 | 37 | ```ruby 38 | sudo raspi-config (enable SSH, disable X windows, setup network) 39 | sudo passwd root (setup root password) 40 | sudo rpi-update 41 | sudo reboot 42 | 43 | sudo nano /etc/apt/sources.list (uncomment deb-src) 44 | sudo apt-get update 45 | sudo apt-get upgrade 46 | sudo reboot 47 | 48 | sudo apt-get build-dep qt4-x11 49 | sudo apt-get build-dep libqt5gui5 50 | sudo apt-get install libudev-dev libinput-dev libts-dev libxcb-xinerama0-dev libxcb-xinerama0 51 | 52 | sudo mkdir /usr/local/qt5pi 53 | sudo chmod -R 777 /usr/local/qt5pi 54 | ``` 55 | 56 | [HOST] 57 | 58 | ```ruby 59 | sudo apt-get install lib32z1 lib32ncurses5 lib32stdc++6 60 | 61 | mkdir ~/raspi 62 | cd ~/raspi 63 | 64 | git clone https://github.com/raspberrypi/tools 65 | 66 | ssh-keygen -t rsa -C pi@raspberrypi.local -N "" -f ~/.ssh/id_rsa 67 | cat ~/.ssh/id_rsa.pub | ssh -o StrictHostKeyChecking=no pi@raspberrypi.local "mkdir -p .ssh && chmod 700 .ssh && cat >> .ssh/authorized_keys" 68 | 69 | mkdir sysroot sysroot/usr sysroot/opt 70 | rsync -avz pi@raspberrypi.local:/lib sysroot 71 | rsync -avz pi@raspberrypi.local:/usr/include sysroot/usr 72 | rsync -avz pi@raspberrypi.local:/usr/lib sysroot/usr 73 | rsync -avz pi@raspberrypi.local:/opt/vc sysroot/opt 74 | 75 | wget https_://raw.githubusercontent.com/riscv/riscv-poky/priv-1.10/scripts/sysroot-relativelinks.py 76 | chmod +x sysroot-relativelinks.py 77 | ./sysroot-relativelinks.py sysroot 78 | 79 | git clone git://code.qt.io/qt/qtbase.git -b 5.11 80 | cd qtbase 81 | 82 | ./configure -no-gbm -no-glib -no-gstreamer -qt-xcb -no-pch -no-zlib -no-use-gold-linker -release -opengl es2 -device linux-rasp-pi-g++ -device-option CROSS_COMPILE=~/raspi/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin/arm-linux-gnueabihf- -sysroot ~/raspi/sysroot -opensource -confirm-license -make libs -prefix /usr/local/qt5pi -extprefix ~/raspi/qt5pi -hostprefix ~/raspi/qt5 -v 83 | 84 | make -j4 85 | make install 86 | ``` 87 | 88 | # Prepare Raspberry Pi Zero 89 | 90 | Download Buildroot https://buildroot.org/download.html and make raspberry immage using included config and overlays. 91 | 92 | I personally flashing image on my Mac OS with Etcher. You may download it here https://www.balena.io/etcher/ or flash image from console. 93 | 94 | Edit wpa_supplicant.conf to set up ssid and psk for you WiFi access point. 95 | 96 | After raspberry boot, enter two console commands: 97 | 98 | ```ruby 99 | mount -o remount, rw / 100 | ln -s /usr/lib/libGLESv2.so /usr/lib/libGLESv2.so.2 101 | ``` 102 | 103 | # Example for make and run application 104 | 105 | [RPI] 106 | 107 | ```ruby 108 | mount -o remount, rw / 109 | ``` 110 | 111 | [HOST] 112 | 113 | ```ruby 114 | cd ~ 115 | git clone https://github.com/helimania/Blackheart.git 116 | cd Blackheart 117 | 118 | ~/raspi/qt5/bin/qmake 119 | make 120 | scp Blackheart root@192.168.1.116:/root 121 | ssh -t root@192.168.1.116 "./Blackheart" 122 | ``` 123 | 124 | After reboot, application runs automaticaly. 125 | 126 | ![preview 2](https://github.com/helimania/Blackheart/blob/master/preview2.jpg) 127 | 128 | # CAN bus interface 129 | 130 | Theoretically can be used CAN bus. For this you need only TJA1050 chip. 131 | 132 | ![TJA1050](https://github.com/helimania/Blackheart/blob/master/ProMini-v2-OLED-Pi/TJA1050.jpg) 133 | 134 | But since the serial interface is used to transfer data to raspberry, you can use i2c or SPI interface to connect this chip to arduino, or separate RX TX lines, for transfer data to raspberry and receive data from CAN bus. Fantasy has not been canceled... 135 | 136 | For example, CAN bus with SPI interface. 137 | 138 | ![MCP2515](https://github.com/helimania/Blackheart/blob/master/ProMini-v2-OLED-Pi/MCP2515-CAN-Bus-Interface.jpg) 139 | 140 | Arduino sketch for example 141 | 142 | ```ruby 143 | #include 144 | #include "mcp_can.h" 145 | 146 | INT32U canId = 0x000; 147 | 148 | unsigned char len = 0; 149 | unsigned char buf[8]; 150 | char str[20]; 151 | 152 | void setup() { 153 | Serial.begin(38400); 154 | 155 | START_INIT: 156 | 157 | if(CAN_OK == CAN.begin(CAN_125KBPS)) { 158 | Serial.println("Initialized successfully"); 159 | } else { 160 | Serial.println("Initializing is failed"); 161 | Serial.println("Reloading..."); 162 | delay(100); 163 | goto START_INIT; 164 | } 165 | } 166 | 167 | void loop() { 168 | if(CAN_MSGAVAIL == CAN.checkReceive()) { 169 | CAN.readMsgBuf(&len, buf); 170 | canId = CAN.getCanId(); 171 | Serial.print("<");Serial.print(canId);Serial.print(","); 172 | for(int i = 0; i"); 176 | Serial.println(); 177 | } 178 | } 179 | ``` 180 | 181 | More details here https://www.14core.com/wiring-the-mcp2515-controller-area-network-can-bus-diagnostics/ 182 | -------------------------------------------------------------------------------- /ProMini-v2-OLED-Pi/ProMini-v2-OLED-Pi.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "Adafruit_MCP23017.h" 4 | #include "MultiMap.h" 5 | #include 6 | #include 7 | 8 | /****************************** 9 | * 10 | * Definition section 11 | * 12 | ******************************/ 13 | 14 | U8X8_SSD1327_MIDAS_128X128_4W_SW_SPI u8x8(12, 13, 11, 10, 9); // u8x8(clock, data, cs, dc, reset) 15 | Adafruit_MCP23017 mcp; // MCP23017 Port Expander 16 | 17 | byte ACC = 4, VOLT = 14, FUEL = 15, TEMP = 16, OIL = 17, DALLAS = 8; // Pin assignment 18 | 19 | int tmI[] = { 0, 10, 20, 30,40,50,60,70,80,90,100,140,200,288,354}; // Temperature sensor values 20 | int tmO[] = {130,120,110,100,90,80,70,65,62,60, 55, 39, 30, 13, 0}; // Temperature real values 21 | 22 | const int oi_num = 10, fu_num = 50, tm_num = 50; // Average constants: 23 | int oi_buf[oi_num], oi_idx = 0; 24 | int fu_buf[fu_num], fu_idx = 0; 25 | int tm_buf[tm_num], tm_idx = 0, tm = 0; 26 | long oi_tot = 0, fu_tot = 0, tm_tot = 0, fu = 0, oi= 0; 27 | volatile unsigned long micros_sp = 0, micros_th = 0, millis_ds = 0, millis_t = 0, millis_d = 0, millis_fu = 0, millis_vo = 0, millis_tm = 0, millis_oi = 0; // Timers 28 | volatile boolean st = false, tt = false; // Triggers 29 | volatile byte sz = 0, tz = 0; // Reset counters 30 | volatile unsigned int sp = 0, th = 0, vo = 0, ds_tm = 0; 31 | unsigned long trip1, trip1_old, trip2; 32 | int pin = 0; 33 | String mcparray; 34 | OneWire ds(DALLAS); // Temperature sensor DS18B20 port 35 | 36 | /****************************** 37 | * 38 | * Setup section 39 | * 40 | ******************************/ 41 | 42 | void setup(){ 43 | Serial.begin(115200); 44 | attachInterrupt(0, spd, FALLING); // Speedometer falling interrupt INT0 45 | attachInterrupt(1, tah, RISING); // Tahometer rising interrupt INT1 46 | mcp.begin(7); // addr 7 = A2 high, A1 high, A0 high 111 47 | for (pin = 0; pin < 16; pin ++) { mcp.pinMode(pin, INPUT); } // MCP23017 readonly init 48 | trip1 = EEPROM_ulong_read(0); // Read Trip1 from EEPROM 49 | trip2 = EEPROM_ulong_read(1); // Read Trip2 from EEPROM 50 | trip1_old = trip1; // Write EEPROM if it changed 51 | 52 | initOLED(); 53 | 54 | for (int idx = 0; idx < oi_num; idx++) oi_buf[idx] = 0; // Buffer reset for oil 55 | for (int idx = 0; idx < fu_num; idx++) fu_buf[idx] = 0; // Buffer reset for fuel 56 | for (int idx = 0; idx < tm_num; idx++) tm_buf[idx] = 0; // Buffer reset for temperature 57 | } 58 | 59 | /****************************** 60 | * 61 | * Main loop section 62 | * 63 | ******************************/ 64 | 65 | void loop(){ 66 | DallasRd(); 67 | if(((millis() - millis_t) >= 20) && digitalRead(ACC)){ // Serial refresh interval in milliseconds if ACC present 68 | millis_t = millis(); 69 | 70 | if ((millis() - millis_fu) >= 20) { // Fuel refresh interval in milliseconds (Full - 70~73 / Empty - ?) 71 | millis_fu = millis(); 72 | fu = constrain(analogRead(FUEL), 70, 200); 73 | fu_tot -= fu_buf[fu_idx]; // Average fuel buffer 74 | fu_buf[fu_idx] = fu; 75 | fu_tot += fu_buf[fu_idx]; 76 | fu_idx++; if (fu_idx >= fu_num) fu_idx = 0; 77 | fu = fu_tot / fu_num; 78 | fu = map(fu, 70, 200, 100, 0); 79 | fu = constrain(fu, 0, 100); 80 | } 81 | 82 | if ((millis() - millis_vo) >= 500) { // Voltage refresh interval in milliseconds 83 | millis_vo = millis(); 84 | vo = analogRead(VOLT); 85 | } 86 | 87 | if ((millis() - millis_tm) >= 20) { // Temperature refresh interval in milliseconds 88 | millis_tm = millis(); 89 | tm = analogRead(TEMP); 90 | tm_tot -= tm_buf[tm_idx]; // Average temperature buffer 91 | tm_buf[tm_idx] = tm; 92 | tm_tot += tm_buf[tm_idx]; 93 | tm_idx++; if (tm_idx >= tm_num) tm_idx = 0; 94 | tm = tm_tot / tm_num; 95 | tm = constrain(multiMap(tm, tmI, tmO, 15), 0, 120); 96 | } 97 | 98 | if ((millis() - millis_oi) >= 10) { // Oil pressure refresh interval in milliseconds 99 | millis_oi = millis(); 100 | //oi = map(analogRead(OIL), 0, 730, 100, 0); 101 | oi = analogRead(OIL); 102 | oi_tot -= oi_buf[oi_idx]; // Average oil buffer 103 | oi_buf[oi_idx] = oi; 104 | oi_tot += oi_buf[oi_idx]; 105 | oi_idx++; if (oi_idx >= oi_num) oi_idx = 0; 106 | oi = oi_tot / oi_num; 107 | oi = map(constrain(oi, 0, 200), 0, 200, 1000, 0); 108 | 109 | } 110 | 111 | mcparray = ""; 112 | for (pin = 0; pin < 16; pin ++) { mcparray += ","; mcparray += mcp.digitalRead(pin); } 113 | 114 | Serial.print(az(sp, 3) + "," + az(th, 4) + "," + az(vo, 4) + "," + az(fu, 3) + "," + tm + "," + az(ds_tm, 3) + "," + az(oi, 4) + mcparray + "," + trip1 + "," + trip2 +"\n"); 115 | 116 | Serial.flush(); 117 | if(tz != 0){tz--;}else{th = 0;}; 118 | if(sz != 0){sz--;}else{sp = 0;}; 119 | } 120 | 121 | if(((millis() - millis_d) >= 100) && digitalRead(ACC)){ // OLED refresh interval in milliseconds if ACC present 122 | millis_d = millis(); 123 | drawOLED(); 124 | } 125 | 126 | if (!digitalRead(ACC) && trip1 > trip1_old) { // Write EEPROM if not ACC present 127 | //EEPROM_ulong_write(0, trip1); 128 | //EEPROM_ulong_write(1, trip2); 129 | //delay(2000); 130 | } 131 | } 132 | 133 | /****************************** 134 | * 135 | * Functions section 136 | * 137 | ******************************/ 138 | 139 | void spd() { // Speedometer interupt 140 | if (!st) { micros_sp = micros(); } 141 | else { sp = 3600000 / 4 / (micros() - micros_sp); } // Sensor pulses per rotation 142 | st = !st; sz = 30; 143 | trip1 ++; trip2 ++; 144 | } 145 | 146 | void tah() { // Tahometer interrupt 147 | if (!tt) { micros_th = micros(); } 148 | else { th = 30000000 / (micros() - micros_th) * 2; } 149 | tt = !tt; tz = 10; 150 | } 151 | 152 | unsigned long EEPROM_ulong_read(int addr) { // Write EEPROM 153 | byte raw[4]; 154 | for(byte i = 0; i < 4; i++) raw[i] = EEPROM.read(addr+i); 155 | unsigned long &num = (unsigned long&)raw; 156 | return num; 157 | } 158 | 159 | void EEPROM_ulong_write(int addr, unsigned long num) { // Read EEPROM 160 | byte raw[4]; 161 | (unsigned long&)raw = num; 162 | for(byte i = 0; i < 4; i++) EEPROM.write(addr+i, raw[i]); 163 | } 164 | 165 | int DallasRd(){ // Read DS18B20 temperature sensor 166 | byte data[2]; 167 | if ((millis() - millis_ds) > 1000){ // DS18B20 read interval in milliseconds 168 | ds.reset(); ds.write(0xCC); ds.write(0xBE); 169 | data[0] = ds.read(); data[1] = ds.read(); 170 | ds_tm = (data[1] << 8) + data[0]; 171 | ds_tm = (ds_tm >> 4) + 200; 172 | ds_tm = constrain(ds_tm, 0, 296); 173 | millis_ds = millis(); 174 | ds.reset(); ds.write(0xCC); ds.write(0x44); // 2 wire connection - (0x44,1) / 3 wire connection (0x44) 175 | } 176 | } 177 | 178 | void initOLED() { // Initialize OLED and draw titles 179 | char * txtOLED[16] = { "Blackheart Board", "", "Speed :", " RPM :", "", "Volts :", " Oil :", " Fuel :", "TempA :", "TempB :", "", "TripA :", "TripB :", "", "PortA :", "PortB :" }; 180 | u8x8.begin(); 181 | u8x8.setPowerSave(0); 182 | u8x8.setFont(u8x8_font_victoriabold8_r); 183 | u8x8.setInverseFont(1); 184 | u8x8.drawString(0, 0,txtOLED[0]); 185 | u8x8.setFont(u8x8_font_chroma48medium8_r); 186 | u8x8.setInverseFont(0); 187 | for (pin = 1; pin < 16; pin ++) u8x8.drawString(0, pin, txtOLED[pin]); 188 | 189 | } 190 | 191 | void drawOLED() { // Redraw OLED 192 | u8x8.setCursor(8, 2); u8x8.print(az(sp, 3)); 193 | u8x8.setCursor(8, 3); u8x8.print(az(th, 4)); 194 | u8x8.setCursor(8, 5); u8x8.print(vo * (15.800 / 1024)); 195 | u8x8.setCursor(8, 6); u8x8.print(az(analogRead(OIL), 4)); 196 | u8x8.setCursor(8, 7); u8x8.print(az(analogRead(FUEL), 4)); 197 | u8x8.setCursor(8, 8); u8x8.print(az(analogRead(TEMP), 4)); 198 | u8x8.setCursor(8, 9); if (ds_tm < 296) u8x8.print(az(ds_tm, 3)); else u8x8.print(" "); 199 | u8x8.setCursor(8, 11); u8x8.print(trip1); 200 | u8x8.setCursor(8, 12); u8x8.print(trip2); 201 | mcparray = ""; 202 | for (pin=0; pin < 8; pin ++) mcparray += mcp.digitalRead(pin); 203 | u8x8.setCursor(8, 14); u8x8.print(mcparray); 204 | mcparray = ""; 205 | for (pin=8; pin < 16; pin ++) mcparray += mcp.digitalRead(pin); 206 | u8x8.setCursor(8, 15); u8x8.print(mcparray); 207 | } 208 | 209 | String az(const int& src, int num) { // Adding zeroes 210 | String result = ""; 211 | if (num > 3) result += (src/1000) % 10; 212 | if (num > 2) result += (src/100) % 10; 213 | if (num > 1) result += (src/10) % 10; 214 | if (num > 0) result += (src) % 10; 215 | if (num == 0) result += src; 216 | return result; 217 | } 218 | --------------------------------------------------------------------------------