/************************************************************************* * Arduino Library for OBD-II UART/I2C Adapter * Distributed under BSD License * Visit http://freematics.com for more information * (C)2012-2016 Stanley Huang *************************************************************************/ #include #include #include "OBD.h" //#define DEBUG Serial uint16_t hex2uint16(const char *p) { char c = *p; uint16_t i = 0; for (char n = 0; c && n < 4; c = *(++p)) { if (c >= 'A' && c <= 'F') { c -= 7; } else if (c>='a' && c<='f') { c -= 39; } else if (c == ' ') { continue; } else if (c < '0' || c > '9') { break; } i = (i << 4) | (c & 0xF); n++; } return i; } byte hex2uint8(const char *p) { byte c1 = *p; byte c2 = *(p + 1); if (c1 >= 'A' && c1 <= 'F') c1 -= 7; else if (c1 >='a' && c1 <= 'f') c1 -= 39; else if (c1 < '0' || c1 > '9') return 0; if (c2 >= 'A' && c2 <= 'F') c2 -= 7; else if (c2 >= 'a' && c2 <= 'f') c2 -= 39; else if (c2 < '0' || c2 > '9') return 0; return c1 << 4 | (c2 & 0xf); } /************************************************************************* * OBD-II UART Adapter *************************************************************************/ byte COBD::sendCommand(const char* cmd, char* buf, byte bufsize, int timeout) { write(cmd); dataIdleLoop(); return receive(buf, bufsize, timeout); } void COBD::sendQuery(byte pid) { char cmd[8]; sprintf(cmd, "%02X%02X\r", dataMode, pid); #ifdef DEBUG debugOutput(cmd); #endif write(cmd); } bool COBD::readPID(byte pid, int& result) { // send a query command sendQuery(pid); // receive and parse the response return getResult(pid, result); } byte COBD::readPID(const byte pid[], byte count, int result[]) { byte results = 0; for (byte n = 0; n < count; n++) { if (readPID(pid[n], result[n])) { results++; } } return results; } byte COBD::readDTC(uint16_t codes[], byte maxCodes) { /* Response example: 0: 43 04 01 08 01 09 1: 01 11 01 15 00 00 00 */ byte codesRead = 0; for (byte n = 0; n < 6; n++) { char buffer[128]; sprintf_P(buffer, n == 0 ? PSTR("03\r") : PSTR("03%02X\r"), n); write(buffer); if (receive(buffer, sizeof(buffer)) > 0) { if (!strstr(buffer, "NO DATA")) { char *p = strstr(buffer, "43"); if (p) { while (codesRead < maxCodes && *p) { p += 6; if (*p == '\r') { p = strchr(p, ':'); if (!p) break; p += 2; } uint16_t code = hex2uint16(p); if (code == 0) break; codes[codesRead++] = code; } } break; } } } return codesRead; } void COBD::clearDTC() { char buffer[32]; write("04\r"); receive(buffer, sizeof(buffer)); } void COBD::write(const char* s) { #ifdef DEBUG DEBUG.print("<<<"); DEBUG.println(s); #endif OBDUART.write(s); } int COBD::normalizeData(byte pid, char* data) { int result; switch (pid) { case PID_RPM: case PID_EVAP_SYS_VAPOR_PRESSURE: // kPa result = getLargeValue(data) >> 2; break; case PID_FUEL_PRESSURE: // kPa result = getSmallValue(data) * 3; break; case PID_COOLANT_TEMP: case PID_INTAKE_TEMP: case PID_AMBIENT_TEMP: case PID_ENGINE_OIL_TEMP: result = getTemperatureValue(data); break; case PID_THROTTLE: case PID_COMMANDED_EGR: case PID_COMMANDED_EVAPORATIVE_PURGE: case PID_FUEL_LEVEL: case PID_RELATIVE_THROTTLE_POS: case PID_ABSOLUTE_THROTTLE_POS_B: case PID_ABSOLUTE_THROTTLE_POS_C: case PID_ACC_PEDAL_POS_D: case PID_ACC_PEDAL_POS_E: case PID_ACC_PEDAL_POS_F: case PID_COMMANDED_THROTTLE_ACTUATOR: case PID_ENGINE_LOAD: case PID_ABSOLUTE_ENGINE_LOAD: case PID_ETHANOL_FUEL: case PID_HYBRID_BATTERY_PERCENTAGE: result = getPercentageValue(data); break; case PID_MAF_FLOW: // grams/sec result = getLargeValue(data) / 100; break; case PID_TIMING_ADVANCE: result = (int)(getSmallValue(data) / 2) - 64; break; case PID_DISTANCE: // km case PID_DISTANCE_WITH_MIL: // km case PID_TIME_WITH_MIL: // minute case PID_TIME_SINCE_CODES_CLEARED: // minute case PID_RUNTIME: // second case PID_FUEL_RAIL_PRESSURE: // kPa case PID_ENGINE_REF_TORQUE: // Nm result = getLargeValue(data); break; case PID_CONTROL_MODULE_VOLTAGE: // V result = getLargeValue(data) / 1000; break; case PID_ENGINE_FUEL_RATE: // L/h result = getLargeValue(data) / 20; break; case PID_ENGINE_TORQUE_DEMANDED: // % case PID_ENGINE_TORQUE_PERCENTAGE: // % result = (int)getSmallValue(data) - 125; break; case PID_SHORT_TERM_FUEL_TRIM_1: case PID_LONG_TERM_FUEL_TRIM_1: case PID_SHORT_TERM_FUEL_TRIM_2: case PID_LONG_TERM_FUEL_TRIM_2: case PID_EGR_ERROR: result = ((int)getSmallValue(data) - 128) * 100 / 128; break; case PID_FUEL_INJECTION_TIMING: result = ((int32_t)getLargeValue(data) - 26880) / 128; break; case PID_CATALYST_TEMP_B1S1: case PID_CATALYST_TEMP_B2S1: case PID_CATALYST_TEMP_B1S2: case PID_CATALYST_TEMP_B2S2: result = getLargeValue(data) / 10 - 40; break; case PID_AIR_FUEL_EQUIV_RATIO: // 0~200 result = (long)getLargeValue(data) * 200 / 65536; break; default: result = getSmallValue(data); } return result; } char* COBD::getResponse(byte& pid, char* buffer, byte bufsize) { while (receive(buffer, bufsize) > 0) { char *p = buffer; while ((p = strstr(p, "41 "))) { p += 3; byte curpid = hex2uint8(p); if (pid == 0) pid = curpid; if (curpid == pid) { errors = 0; p += 2; if (*p == ' ') return p + 1; } } } return 0; } bool COBD::getResult(byte& pid, int& result) { char buffer[64]; char* data = getResponse(pid, buffer, sizeof(buffer)); if (!data) { recover(); errors++; return false; } result = normalizeData(pid, data); return true; } void COBD::enterLowPowerMode() { char buf[32]; sendCommand("ATLP\r", buf, sizeof(buf)); } void COBD::leaveLowPowerMode() { // simply send any command to wake the device up char buf[32]; sendCommand("ATI\r", buf, sizeof(buf), 1000); } char* COBD::getResultValue(char* buf) { char* p = buf; for (;;) { if (isdigit(*p) || *p == '-') { return p; } p = strchr(p, '\r'); if (!p) break; if (*(++p) == '\n') p++; } return 0; } float COBD::getVoltage() { char buf[32]; if (sendCommand("ATRV\r", buf, sizeof(buf)) > 0) { char* p = getResultValue(buf); if (p) return atof(p); } return 0; } bool COBD::getVIN(char* buffer, byte bufsize) { if (sendCommand("0902\r", buffer, bufsize)) { char *p = strstr(buffer, "0: 49 02"); if (p) { char *q = buffer; p += 10; do { for (++p; *p == ' '; p += 3) { if (*q = hex2uint8(p + 1)) q++; } p = strchr(p, ':'); } while(p); *q = 0; return true; } } return false; } bool COBD::isValidPID(byte pid) { if (pid >= 0x7f) return true; pid--; byte i = pid >> 3; byte b = 0x80 >> (pid & 0x7); return pidmap[i] & b; } byte COBD::begin() { long baudrates[] = {38400, 115200}; byte version = 0; for (byte n = 0; n < sizeof(baudrates) / sizeof(baudrates[0]) && version == 0; n++) { OBDUART.begin(baudrates[n]); version = getVersion(); } return version; } byte COBD::getVersion() { byte version = 0; for (byte n = 0; n < 3; n++) { char buffer[32]; if (sendCommand("ATI\r", buffer, sizeof(buffer), 200)) { char *p = strchr(buffer, ' '); if (p) { p += 2; version = (*p - '0') * 10 + (*(p + 2) - '0'); break; } } } return version; } byte COBD::receive(char* buffer, byte bufsize, int timeout) { unsigned char n = 0; unsigned long startTime = millis(); char c = 0; for (;;) { if (OBDUART.available()) { c = OBDUART.read(); if (!buffer) { n++; } else if (n < bufsize - 1) { if (c == '.' && n > 2 && buffer[n - 1] == '.' && buffer[n - 2] == '.') { // waiting siginal n = 0; timeout = OBD_TIMEOUT_LONG; } else { if (c == '\r' || c == '\n' || c == ' ') { if (n == 0 || buffer[n - 1] == '\r' || buffer[n - 1] == '\n') continue; } buffer[n++] = c; } } } else { if (c == '>') { // prompt char received break; } if (millis() - startTime > timeout) { // timeout break; } dataIdleLoop(); } } if (buffer) { buffer[n] = 0; } #ifdef DEBUG DEBUG.print(">>>"); DEBUG.println(buffer); #endif return n; } void COBD::recover() { sendCommand("\r", 0, 0); } bool COBD::init(OBD_PROTOCOLS protocol) { const char *initcmd[] = {"ATZ\r", "ATE0\r", "ATH0\r"}; char buffer[64]; m_state = OBD_DISCONNECTED; for (unsigned char i = 0; i < sizeof(initcmd) / sizeof(initcmd[0]); i++) { write(initcmd[i]); if (receive(buffer, sizeof(buffer), OBD_TIMEOUT_LONG) == 0) { return false; } } if (protocol != PROTO_AUTO) { sprintf_P(buffer, PSTR("ATSP %u\r"), protocol); write(buffer); if (receive(buffer, sizeof(buffer), OBD_TIMEOUT_LONG) == 0 && !strstr(buffer, "OK")) { return false; } } // load pid map memset(pidmap, 0, sizeof(pidmap)); bool success = false; for (byte i = 0; i < 4; i++) { byte pid = i * 0x20; sendQuery(pid); if (receive(buffer, sizeof(buffer), OBD_TIMEOUT_LONG) > 0) { char *p = buffer; while ((p = strstr(p, "41 "))) { p += 3; if (hex2uint8(p) == pid) { p += 2; for (byte n = 0; n < 4 && *(p + n * 3) == ' '; n++) { pidmap[i * 4 + n] = hex2uint8(p + n * 3 + 1); } success = true; } } } } if (success) { m_state = OBD_CONNECTED; errors = 0; } return success; } void COBD::end() { m_state = OBD_DISCONNECTED; OBDUART.end(); } bool COBD::setBaudRate(unsigned long baudrate) { OBDUART.print("ATBR1 "); OBDUART.print(baudrate); OBDUART.print('\r'); delay(50); OBDUART.end(); OBDUART.begin(baudrate); recover(); return true; } bool COBD::memsInit() { char buf[16]; return sendCommand("ATTEMP\r", buf, sizeof(buf)) > 0 && !strchr(buf, '?'); } bool COBD::memsRead(int* acc, int* gyr, int* mag, int* temp) { char buf[64]; bool success; if (acc) { success = false; if (sendCommand("ATACL\r", buf, sizeof(buf)) > 0) do { char* p = getResultValue(buf); if (!p) break; acc[0] = atoi(p++); if (!(p = strchr(p, ','))) break; acc[1] = atoi(++p); if (!(p = strchr(p, ','))) break; acc[2] = atoi(++p); success = true; } while (0); if (!success) return false; } if (gyr) { success = false; if (sendCommand("ATGYRO\r", buf, sizeof(buf)) > 0) do { char* p = getResultValue(buf); if (!p) break; gyr[0] = atoi(p++); if (!(p = strchr(p, ','))) break; gyr[1] = atoi(++p); if (!(p = strchr(p, ','))) break; gyr[2] = atoi(++p); success = true; } while (0); if (!success) return false; } if (temp) { success = false; if (sendCommand("ATTEMP\r", buf, sizeof(buf)) > 0) { char* p = getResultValue(buf); if (p) { *temp = (atoi(p) + 12412) / 34; success = true; } } if (!success) return false; } return true; } #ifdef DEBUG void COBD::debugOutput(const char *s) { DEBUG.print('['); DEBUG.print(millis()); DEBUG.print(']'); DEBUG.print(s); } #endif /************************************************************************* * OBD-II I2C Adapter *************************************************************************/ byte COBDI2C::begin() { Wire.begin(); #ifdef DEBUG DEBUG.begin(115200); #endif recover(); return getVersion(); } void COBDI2C::end() { m_state = OBD_DISCONNECTED; } void COBDI2C::write(const char* s) { COMMAND_BLOCK cmdblock = {millis(), CMD_SEND_AT_COMMAND}; Wire.beginTransmission(I2C_ADDR); Wire.write((byte*)&cmdblock, sizeof(cmdblock)); Wire.write(s); Wire.endTransmission(); } bool COBDI2C::sendCommandBlock(byte cmd, uint8_t data, byte* payload, byte payloadBytes) { COMMAND_BLOCK cmdblock = {millis(), cmd, data}; Wire.beginTransmission(I2C_ADDR); bool success = Wire.write((byte*)&cmdblock, sizeof(COMMAND_BLOCK)) == sizeof(COMMAND_BLOCK); if (payload) Wire.write(payload, payloadBytes); Wire.endTransmission(); return success; } byte COBDI2C::receive(char* buffer, byte bufsize, int timeout) { uint32_t start = millis(); byte offset = 0; do { Wire.requestFrom((byte)I2C_ADDR, (byte)MAX_PAYLOAD_SIZE, (byte)1); int c = Wire.read(); if (offset == 0 && c < 0xa) { // data not ready dataIdleLoop(); continue; } if (buffer) buffer[offset++] = c; for (byte i = 1; i < MAX_PAYLOAD_SIZE && Wire.available(); i++) { char c = Wire.read(); if (c == '.' && offset > 2 && buffer[offset - 1] == '.' && buffer[offset - 2] == '.') { // waiting signal offset = 0; timeout = OBD_TIMEOUT_LONG; } else if (c == 0 || offset == bufsize - 1) { // string terminator encountered or buffer full if (buffer) buffer[offset] = 0; // discard the remaining data while (Wire.available()) Wire.read(); return offset; } else { if (buffer) buffer[offset++] = c; } } } while(millis() - start < timeout); if (buffer) buffer[offset] = 0; return 0; } void COBDI2C::setQueryPID(byte pid, byte obdPid[]) { byte n = 0; for (; n < MAX_PIDS && obdPid[n]; n++) { if (obdPid[n] == pid) return; } if (n == MAX_PIDS) { memmove(obdPid, obdPid + 1, sizeof(obdPid[0]) * (MAX_PIDS - 1)); n = MAX_PIDS - 1; } obdPid[n] = pid; } void COBDI2C::applyQueryPIDs(byte obdPid[]) { sendCommandBlock(CMD_APPLY_OBD_PIDS, 0, (byte*)obdPid, sizeof(obdPid[0])* MAX_PIDS); delay(200); } void COBDI2C::loadQueryData(PID_INFO obdInfo[]) { sendCommandBlock(CMD_LOAD_OBD_DATA); dataIdleLoop(); Wire.requestFrom((byte)I2C_ADDR, (byte)MAX_PAYLOAD_SIZE, (byte)0); Wire.readBytes((char*)obdInfo, sizeof(obdInfo[0]) * MAX_PIDS); } #define MPU6050_I2C_ADDRESS 0x68 #define MPU6050_ACCEL_XOUT_H 0x3B // R #define MPU6050_ACCEL_XOUT_L 0x3C // R #define MPU6050_ACCEL_YOUT_H 0x3D // R #define MPU6050_ACCEL_YOUT_L 0x3E // R #define MPU6050_ACCEL_ZOUT_H 0x3F // R #define MPU6050_ACCEL_ZOUT_L 0x40 // R #define MPU6050_TEMP_OUT_H 0x41 // R #define MPU6050_TEMP_OUT_L 0x42 // R #define MPU6050_GYRO_XOUT_H 0x43 // R #define MPU6050_GYRO_XOUT_L 0x44 // R #define MPU6050_GYRO_YOUT_H 0x45 // R #define MPU6050_GYRO_YOUT_L 0x46 // R #define MPU6050_GYRO_ZOUT_H 0x47 // R #define MPU6050_GYRO_ZOUT_L 0x48 // R #define MPU6050_PWR_MGMT_1 0x6B // R/W #define MPU6050_PWR_MGMT_2 0x6C // R/W #define MPU6050_WHO_AM_I 0x75 // R typedef struct { uint8_t x_accel_h; uint8_t x_accel_l; uint8_t y_accel_h; uint8_t y_accel_l; uint8_t z_accel_h; uint8_t z_accel_l; uint8_t t_h; uint8_t t_l; uint8_t x_gyro_h; uint8_t x_gyro_l; uint8_t y_gyro_h; uint8_t y_gyro_l; uint8_t z_gyro_h; uint8_t z_gyro_l; } MPU6050_READOUT_DATA; bool COBDI2C::memsInit() { // default at power-up: // Gyro at 250 degrees second // Acceleration at 2g // Clock source at internal 8MHz // The device is in sleep mode. // uint8_t c; bool success; success = MPU6050_read (MPU6050_WHO_AM_I, &c, 1); if (!success) return false; // According to the datasheet, the 'sleep' bit // should read a '1'. But I read a '0'. // That bit has to be cleared, since the sensor // is in sleep mode at power-up. Even if the // bit reads '0'. success = MPU6050_read (MPU6050_PWR_MGMT_2, &c, 1); if (!success) return false; // Clear the 'sleep' bit to start the sensor. MPU6050_write_reg (MPU6050_PWR_MGMT_1, 0); return true; } bool COBDI2C::memsRead(int* acc, int* gyr, int* mag, int* temp) { bool success; // Read the raw values. // Read 14 bytes at once, // containing acceleration, temperature and gyro. // With the default settings of the MPU-6050, // there is no filter enabled, and the values // are not very stable. MPU6050_READOUT_DATA accel_t_gyro; success = MPU6050_read (MPU6050_ACCEL_XOUT_H, (uint8_t *)&accel_t_gyro, sizeof(MPU6050_READOUT_DATA)); if (!success) return false; if (temp) { // 340 per degrees Celsius, -512 at 35 degrees. *temp = ((int)(((uint16_t)accel_t_gyro.t_h << 8) | accel_t_gyro.t_l) + 512) / 34 + 350; } if (acc) { MPU6050_store(acc, accel_t_gyro.x_accel_l, accel_t_gyro.x_accel_h); MPU6050_store(acc + 1, accel_t_gyro.y_accel_l, accel_t_gyro.y_accel_h); MPU6050_store(acc + 2, accel_t_gyro.z_accel_l, accel_t_gyro.z_accel_h); } if (gyr) { MPU6050_store(gyr, accel_t_gyro.x_gyro_l, accel_t_gyro.x_gyro_h); MPU6050_store(gyr + 1, accel_t_gyro.y_gyro_l, accel_t_gyro.y_gyro_h); MPU6050_store(gyr + 2, accel_t_gyro.z_gyro_l, accel_t_gyro.z_gyro_h); } if (mag) { // no magnetometer mag[0] = 0; mag[1] = 0; mag[2] = 0; } return true; } void COBDI2C::MPU6050_store(int* pData, uint8_t data_l, uint8_t data_h) { uint8_t* ptr = (uint8_t*)pData; *ptr = data_l; *(ptr + 1) = data_h; } bool COBDI2C::MPU6050_read(int start, uint8_t* buffer, int size) { int i, n; Wire.beginTransmission(MPU6050_I2C_ADDRESS); Wire.write(start); Wire.endTransmission(false); // hold the I2C-bus // Third parameter is true: relase I2C-bus after data is read. Wire.requestFrom(MPU6050_I2C_ADDRESS, size, true); while(Wire.available() && i