Unlock hidden diagnostic data straight from your vehicle's engine control network. Assemble an Arduino diagnostic unit that polls PIDs over the high-speed CAN bus network and draws beautiful metrics on a mini OLED screen.
Vehicle diagnostic ports utilize standard Parameter IDs (PIDs) defined under the SAE J1979 framework. A diagnostic gauge polls the vehicle ECU by sending requests.
To ask for data, the controller transmits a CAN frame with ID 0x7DF containing the payload
service ID and PID hex. The vehicle's ECU responds on ID 0x7E8 containing calculated parameter
bytes.
01 0C (Engine RPM): Value = ((A × 256) + B) / 4
01 05 (Coolant Temp): Value = A - 40 (Degrees Celsius)
01 11 (Throttle Position): Value = (A × 100) / 255 (Percent)
Arduino microcontrollers are too slow to read raw 500kbps CAN networks directly. We use an MCP2515 CAN Controller chip with a TJA1050 driver interface.
The MCP2515 buffers incoming high-speed bitstreams, filters out unrelated frames, and relays data packet variables to the Arduino over a standard SPI bus: MOSI (Master Out Slave In), MISO (Master In Slave Out), SCK (Serial Clock), and CS (Chip Select).
Everything you need to source to build this custom OBD-II OLED gauge module. Total cost is under $30.
Coordinates PID loop query commands and drives graphic buffers on the I2C OLED screen.
~$4.00Receives and transmits vehicle network packets. TJA1050 translates physical voltage differences.
~$5.00Miniature 0.96" high-contrast graphical display. Connects via 4 wires over standard I2C SCL/SDA.
~$4.00Taps into car battery line inside OBD port connector and regulates logic voltage rails.
~$2.00A plastic 16-pin male OBD-II enclosure shell to neatly mount diagnostic wires inside.
~$6.00Hover or click on schematic elements to view pinout instructions, SPI cables, and I2C buses.
Hover over any module or wire line in the schematic to inspect its installation details and connectivity logic.
Follow the assembly slides sequentially to wire and mount the CAN bus gauge.
Upload this sketch program to your Arduino Nano board using the official Arduino IDE tool.
#include <SPI.h>
#include <mcp_can.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define CAN_CS_PIN 10
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
MCP_CAN CAN(CAN_CS_PIN);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
unsigned long lastQueryTime = 0;
int currentTemp = 0;
int currentThrottle = 0;
void setup() {
Wire.begin();
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(10, 20);
display.print("Initializing...");
display.display();
// Try to initialize CAN at 500kbps (Standard OBD speed)
while (CAN.begin(MCP_ANY, CAN_500KBPS, MCP_8MHZ) != CAN_OK) {
display.clearDisplay();
display.setCursor(10, 20);
display.print("CAN Init Failed!");
display.display();
delay(500);
}
CAN.setMode(MCP_NORMAL);
}
void loop() {
unsigned long currentTime = millis();
if (currentTime - lastQueryTime >= 200) { // Query every 200ms
lastQueryTime = currentTime;
queryOBD2PID(0x05); // Query Coolant Temperature
delay(20);
queryOBD2PID(0x11); // Query Throttle Position
updateScreen();
}
}
void queryOBD2PID(byte pid) {
// standard OBD frame query payload
byte payload[8] = {0x02, 0x01, pid, 0x00, 0x00, 0x00, 0x00, 0x00};
// Send 11-bit standard query frame
CAN.sendMsgBuf(0x7DF, 0, 8, payload);
// Wait short period and read buffer responses
delay(10);
long unsigned int rxId;
unsigned char len = 0;
unsigned char rxBuf[8];
if (CAN.readMsgBuf(&rxId, &len, rxBuf) == CAN_OK) {
if (rxId == 0x7E8 && rxBuf[1] == 0x41) { // Positive OBD response
if (rxBuf[2] == 0x05) {
currentTemp = rxBuf[3] - 40; // Coolant calculation
} else if (rxBuf[2] == 0x11) {
currentThrottle = (rxBuf[3] * 100) / 255; // Throttle calculation
}
}
}
}
void updateScreen() {
display.clearDisplay();
// Header text
display.setTextSize(1);
display.setCursor(0, 0);
display.print("OBD-II LIVE TELEMETRY");
// Draw Coolant Temperature
display.setTextSize(2);
display.setCursor(0, 16);
display.print("TEMP: ");
display.print(currentTemp);
display.print(" C");
// Draw Throttle Bar
display.setTextSize(1);
display.setCursor(0, 42);
display.print("THR: ");
display.print(currentThrottle);
display.print("%");
// Drawing throttle outline bar
display.drawRect(50, 42, 75, 10, WHITE);
int fillWidth = (currentThrottle * 71) / 100;
display.fillRect(52, 44, fillWidth, 6, WHITE);
display.display();
}
Adjust the coolant and throttle sliders. Select different virtual layout buttons to change what renders on the virtual SSD1306 OLED panel screen.