diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0511e2b --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d3460d5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,15 @@ +[submodule "libraries/SevSeg"] + path = libraries/SevSeg + url = https://github.com/outadoc/SevSeg +[submodule "libraries/MsTimer2"] + path = libraries/MsTimer2 + url = https://github.com/PaulStoffregen/MsTimer2 +[submodule "libraries/TimerOne"] + path = libraries/TimerOne + url = https://github.com/PaulStoffregen/TimerOne +[submodule "libraries/NeoSWSerial"] + path = libraries/NeoSWSerial + url = https://github.com/SlashDevin/NeoSWSerial +[submodule "libraries/narcoleptic"] + path = libraries/narcoleptic + url = https://github.com/brabl2/narcoleptic diff --git a/cad/bttf_speedo_digit_frame.stl b/cad/bttf_speedo_digit_frame.stl new file mode 100644 index 0000000..1e8fb69 Binary files /dev/null and b/cad/bttf_speedo_digit_frame.stl differ diff --git a/datasheets/2061261_potentiometer.pdf b/datasheets/2061261_potentiometer.pdf new file mode 100644 index 0000000..5925936 Binary files /dev/null and b/datasheets/2061261_potentiometer.pdf differ diff --git a/datasheets/2190206_display.pdf b/datasheets/2190206_display.pdf new file mode 100644 index 0000000..51ba5c4 Binary files /dev/null and b/datasheets/2190206_display.pdf differ diff --git a/datasheets/82083_enclosure.pdf b/datasheets/82083_enclosure.pdf new file mode 100644 index 0000000..94600b5 Binary files /dev/null and b/datasheets/82083_enclosure.pdf differ diff --git a/datasheets/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_Datasheet.pdf b/datasheets/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_Datasheet.pdf new file mode 100644 index 0000000..e98e8dc Binary files /dev/null and b/datasheets/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_Datasheet.pdf differ diff --git a/datasheets/nano_pinout.pdf b/datasheets/nano_pinout.pdf new file mode 100644 index 0000000..1ee7822 Binary files /dev/null and b/datasheets/nano_pinout.pdf differ diff --git a/dmc_speedometer.ino b/dmc_speedometer.ino deleted file mode 100644 index 8787638..0000000 --- a/dmc_speedometer.ino +++ /dev/null @@ -1,122 +0,0 @@ -#include -#include -#include -#include - -#define STATE_DISCONNECTED 0 -#define STATE_CONNECTED 1 - -//#define DEBUG - -typedef int speed_t; - -SevSeg sevseg; -COBD obd; - -volatile byte state; - -void setup() { - state = STATE_DISCONNECTED; - - #ifdef DEBUG - Serial.begin(115200); - #endif - - setupDisplay(); - - Timer1.initialize(10000); - Timer1.attachInterrupt(refreshDisplay); -} - -void setupDisplay() { - byte numDigits = 2; - byte digitPins[] = {3, 4}; - byte segmentPins[] = {5, 6, 7, 8, 9, 10, 11, 12}; - bool resistorsOnSegments = true; // 'false' means resistors are on digit pins - byte hardwareConfig = COMMON_CATHODE; // See README.md for options - bool updateWithDelays = false; // Default. Recommended - bool leadingZeros = false; // Use 'true' if you'd like to keep the leading zeros - - sevseg.begin(hardwareConfig, numDigits, digitPins, segmentPins, resistorsOnSegments, updateWithDelays, leadingZeros); -} - -void setupObdConnection() { - #ifndef DEBUG - obd.begin(); - - // initialize OBD-II adapter - for (;;) { - int value; - // Try to init and read speed; if we can't do either of them, sleep for a while - if (obd.init() && obd.readPID(PID_SPEED, value)) - break; - - state = STATE_DISCONNECTED; - - obd.enterLowPowerMode(); - Narcoleptic.delay(7000); - obd.leaveLowPowerMode(); - } - #endif - - #ifdef DEBUG - delay(5000); - #endif - - state = STATE_CONNECTED; -} - -void loop() { - if (state == STATE_DISCONNECTED) { - // Clear display if we couldn't read the speed, and try reconnecting - sevseg.blank(); - setupObdConnection(); - } - - speed_t speed = readCurrentSpeed(); - speed_t adjustedSpeed = adjustSpeed(speed); - - // Display last read speed if things are ok - if (adjustedSpeed < 100) { - sevseg.setNumber(adjustedSpeed, 0); - } else { - // If the speed is >= 100, don't display the hundreds - // (useful when reading in km/h) - sevseg.setNumber(adjustedSpeed - 100, 0); - } -} - -// Called every 10 ms -void refreshDisplay() { - sevseg.refreshDisplay(); -} - -speed_t adjustSpeed(speed_t speed) { - float modifier = (float)map(analogRead(0), 0, 1024, 2000, 18000) / (float)10000.0; - - #ifdef DEBUG - Serial.print("modifier: "); - Serial.println(modifier, 4); - #endif - - return round(modifier * (float)speed); -} - -speed_t readCurrentSpeed() { - #ifndef DEBUG - int value; - if (obd.readPID(PID_SPEED, value)) { - return value; - } - - state = STATE_DISCONNECTED; - return -1; - #endif - - #ifdef DEBUG - delay(300); - return 140; - #endif -} - - diff --git a/dmc_speedometer/dmc_speedometer.ino b/dmc_speedometer/dmc_speedometer.ino new file mode 100644 index 0000000..818e037 --- /dev/null +++ b/dmc_speedometer/dmc_speedometer.ino @@ -0,0 +1,204 @@ +#include +#include +#include +#include +#include + +#define STATE_DISCONNECTED 0x0 +#define STATE_CONNECTED 0x2 + +#define TIMER_INTERVAL_DISP_REFRESH_MS 10 +#define TIMER_INTERVAL_DISP_INC_MS 500 + +#define PIN_SEG_A 5 +#define PIN_SEG_B 6 +#define PIN_SEG_C 7 +#define PIN_SEG_D 8 +#define PIN_SEG_E 9 +#define PIN_SEG_F 10 +#define PIN_SEG_G 11 +#define PIN_SEG_DP 12 + +#define PIN_DIG_1 A1 +#define PIN_DIG_2 A2 + +#define PIN_SPEED_ADJUST 0 + +//#define MODE_SIMULATION + +typedef uint8_t speed_t; + +SevSeg sevseg; +COBD obd; + +volatile byte state; +volatile speed_t target_read_speed; + +float modifier; + +void setup() { + state = STATE_DISCONNECTED; + target_read_speed = 0; + + // Read speed modifier (1.0 keeps raw speed read from OBD) + // Play with the potentiometer to adjust to real speed or switch to mph + modifier = (float)map(analogRead(PIN_SPEED_ADJUST), 0, 1024, 2000, 18000) / (float)10000.0; + + setup_display(); + + // Wait a little for everything to settle before we move on + delay(2000); + + setup_timers(); +} + +void setup_timers() { + Timer1.initialize(TIMER_INTERVAL_DISP_REFRESH_MS * 1000); + Timer1.attachInterrupt(isr_refresh_display); + + MsTimer2::set(TIMER_INTERVAL_DISP_INC_MS, isr_display); + MsTimer2::start(); +} + +void setup_display() { + byte numDigits = 2; + byte digitPins[] = {PIN_DIG_1, PIN_DIG_2}; + byte segmentPins[] = { + PIN_SEG_A, + PIN_SEG_B, + PIN_SEG_C, + PIN_SEG_D, + PIN_SEG_E, + PIN_SEG_F, + PIN_SEG_G, + PIN_SEG_DP + }; + bool resistorsOnSegments = true; + byte hardwareConfig = COMMON_CATHODE; + bool updateWithDelays = false; + bool leadingZeros = false; + + sevseg.begin(hardwareConfig, numDigits, digitPins, segmentPins, + resistorsOnSegments, updateWithDelays, leadingZeros); + + sevseg.blank(); +} + +void setup_obd_connection() { +#ifndef MODE_SIMULATION + obd.begin(); + + // initialize OBD-II adapter + for (;;) { + int value; + // Try to init and read speed; if we can't do either of them, sleep for a while + if (obd.init() && obd.readPID(PID_SPEED, value)) { + // Connection established! + break; + } + + state = STATE_DISCONNECTED; + + // Enter deep sleep; disable all timers, serial comm., interrupts, etc. + obd.enterLowPowerMode(); + Narcoleptic.delay(8000); + obd.leaveLowPowerMode(); + } +#else + delay(1000); +#endif + + state = STATE_CONNECTED; +} + +void loop() { + if (state == STATE_DISCONNECTED) { + // Clear display if we couldn't read the speed, and try reconnecting + sevseg.blank(); + setup_obd_connection(); + } + + probe_current_speed(); +} + +void isr_display() { + // Speed currently displayed; will be incremented to reach target speed + static speed_t curr_disp_speed = 0; + + if (state == STATE_DISCONNECTED) { + return; + } + + // Make copy of target speed + const speed_t target_speed = adjust_speed(target_read_speed); + + // We want to increment speed one by one until we hit the target speed + // on a relatively short duration + double interval_between_incs = TIMER_INTERVAL_DISP_INC_MS / (abs(target_speed - curr_disp_speed)); + + // Enable interrupts for the rest of the procedure, + // because we want the display to still be refreshed + interrupts(); + + if (curr_disp_speed == target_speed) { + set_displayed_speed(curr_disp_speed); + } + + // Until we've hit the target speed, increment, display and pause + while (curr_disp_speed != target_speed) { + if (curr_disp_speed < target_speed) { + curr_disp_speed++; + } else { + curr_disp_speed--; + } + + // Display and pause execution for a fixed amount of time between each iteration + set_displayed_speed(curr_disp_speed); + delay(interval_between_incs); + } +} + +// Called every 10 ms +void isr_refresh_display() { + sevseg.refreshDisplay(); +} + +speed_t adjust_speed(speed_t speed) { + return round(modifier * (float)speed); +} + +void set_displayed_speed(speed_t speed) { + if (speed < 100) { + sevseg.setNumber(speed, 0); + } else { + // If the speed is >= 100, truncate display and don't show the hundreds + // (useful when reading in km/h) + sevseg.setNumber(speed - 100, 0); + } +} + +void probe_current_speed() { + if (state == STATE_DISCONNECTED) + return; + +#ifndef MODE_SIMULATION + int value; + if (obd.readPID(PID_SPEED, value)) { + noInterrupts(); + target_read_speed = value; + interrupts(); + return; + } + + noInterrupts(); + state = STATE_DISCONNECTED; + target_read_speed = 0; + interrupts(); +#else + delay(50); + + noInterrupts(); + target_read_speed = (millis() / 1000 * 5) % 99; + interrupts(); +#endif +} diff --git a/libraries/MsTimer2 b/libraries/MsTimer2 new file mode 160000 index 0000000..601e4e1 --- /dev/null +++ b/libraries/MsTimer2 @@ -0,0 +1 @@ +Subproject commit 601e4e1f54e10e8b782eaac88056fa9b2dbcce06 diff --git a/libraries/NeoSWSerial b/libraries/NeoSWSerial new file mode 160000 index 0000000..d5b18b2 --- /dev/null +++ b/libraries/NeoSWSerial @@ -0,0 +1 @@ +Subproject commit d5b18b287f3e208ce8629642d94fa34a51072c46 diff --git a/libraries/OBD2UART/OBD2UART.cpp b/libraries/OBD2UART/OBD2UART.cpp new file mode 100755 index 0000000..0cb72f1 --- /dev/null +++ b/libraries/OBD2UART/OBD2UART.cpp @@ -0,0 +1,541 @@ +/************************************************************************* +* Arduino Library for Freematics OBD-II UART Adapter +* Distributed under BSD License +* Visit http://freematics.com for more information +* (C)2012-2016 Stanley Huang +*************************************************************************/ + +#include "OBD2UART.h" + +//#define DEBUG Serial + +#ifdef ESP32 +extern HardwareSerial Serial1; +#endif + +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 (float)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) != 0; +} + +byte COBD::begin() +{ + long baudrates[] = {115200, 38400}; + byte version = 0; + for (byte n = 0; n < sizeof(baudrates) / sizeof(baudrates[0]); n++) { +#ifndef ESP32 + OBDUART.begin(baudrates[n]); +#else + OBDUART.begin(baudrates[n], SERIAL_8N1, 16, 17); +#endif + version = getVersion(); + if (version != 0) break; + OBDUART.end(); + } + 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 ((int)(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(int16_t* acc, int16_t* gyr, int16_t* mag, int16_t* 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 + diff --git a/libraries/OBD2UART/OBD2UART.h b/libraries/OBD2UART/OBD2UART.h new file mode 100755 index 0000000..62ac18b --- /dev/null +++ b/libraries/OBD2UART/OBD2UART.h @@ -0,0 +1,193 @@ +/************************************************************************* +* Arduino Library for Freematics OBD-II UART Adapter +* Distributed under BSD License +* Visit http://freematics.com for more information +* (C)2012-2016 Stanley Huang +*************************************************************************/ + +#include + +#define OBD_TIMEOUT_SHORT 1000 /* ms */ +#define OBD_TIMEOUT_LONG 5000 /* ms */ +#define OBD_TIMEOUT_GPS 200 /* ms */ + +#ifndef OBDUART +#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168P__) +#define OBDUART Serial +#else +#define OBDUART Serial1 +#endif +#endif + +// Mode 1 PIDs +#define PID_ENGINE_LOAD 0x04 +#define PID_COOLANT_TEMP 0x05 +#define PID_SHORT_TERM_FUEL_TRIM_1 0x06 +#define PID_LONG_TERM_FUEL_TRIM_1 0x07 +#define PID_SHORT_TERM_FUEL_TRIM_2 0x08 +#define PID_LONG_TERM_FUEL_TRIM_2 0x09 +#define PID_FUEL_PRESSURE 0x0A +#define PID_INTAKE_MAP 0x0B +#define PID_RPM 0x0C +#define PID_SPEED 0x0D +#define PID_TIMING_ADVANCE 0x0E +#define PID_INTAKE_TEMP 0x0F +#define PID_MAF_FLOW 0x10 +#define PID_THROTTLE 0x11 +#define PID_AUX_INPUT 0x1E +#define PID_RUNTIME 0x1F +#define PID_DISTANCE_WITH_MIL 0x21 +#define PID_COMMANDED_EGR 0x2C +#define PID_EGR_ERROR 0x2D +#define PID_COMMANDED_EVAPORATIVE_PURGE 0x2E +#define PID_FUEL_LEVEL 0x2F +#define PID_WARMS_UPS 0x30 +#define PID_DISTANCE 0x31 +#define PID_EVAP_SYS_VAPOR_PRESSURE 0x32 +#define PID_BAROMETRIC 0x33 +#define PID_CATALYST_TEMP_B1S1 0x3C +#define PID_CATALYST_TEMP_B2S1 0x3D +#define PID_CATALYST_TEMP_B1S2 0x3E +#define PID_CATALYST_TEMP_B2S2 0x3F +#define PID_CONTROL_MODULE_VOLTAGE 0x42 +#define PID_ABSOLUTE_ENGINE_LOAD 0x43 +#define PID_AIR_FUEL_EQUIV_RATIO 0x44 +#define PID_RELATIVE_THROTTLE_POS 0x45 +#define PID_AMBIENT_TEMP 0x46 +#define PID_ABSOLUTE_THROTTLE_POS_B 0x47 +#define PID_ABSOLUTE_THROTTLE_POS_C 0x48 +#define PID_ACC_PEDAL_POS_D 0x49 +#define PID_ACC_PEDAL_POS_E 0x4A +#define PID_ACC_PEDAL_POS_F 0x4B +#define PID_COMMANDED_THROTTLE_ACTUATOR 0x4C +#define PID_TIME_WITH_MIL 0x4D +#define PID_TIME_SINCE_CODES_CLEARED 0x4E +#define PID_ETHANOL_FUEL 0x52 +#define PID_FUEL_RAIL_PRESSURE 0x59 +#define PID_HYBRID_BATTERY_PERCENTAGE 0x5B +#define PID_ENGINE_OIL_TEMP 0x5C +#define PID_FUEL_INJECTION_TIMING 0x5D +#define PID_ENGINE_FUEL_RATE 0x5E +#define PID_ENGINE_TORQUE_DEMANDED 0x61 +#define PID_ENGINE_TORQUE_PERCENTAGE 0x62 +#define PID_ENGINE_REF_TORQUE 0x63 + +// non-OBD/custom PIDs (no mode number) +#define PID_GPS_LATITUDE 0xA +#define PID_GPS_LONGITUDE 0xB +#define PID_GPS_ALTITUDE 0xC +#define PID_GPS_SPEED 0xD +#define PID_GPS_HEADING 0xE +#define PID_GPS_SAT_COUNT 0xF +#define PID_GPS_TIME 0x10 +#define PID_GPS_DATE 0x11 +#define PID_ACC 0x20 +#define PID_GYRO 0x21 +#define PID_COMPASS 0x22 +#define PID_MEMS_TEMP 0x23 +#define PID_BATTERY_VOLTAGE 0x24 + +// custom PIDs for calculated data +#define PID_TRIP_DISTANCE 0x30 + +typedef enum { + PROTO_AUTO = 0, + PROTO_ISO_9141_2 = 3, + PROTO_KWP2000_5KBPS = 4, + PROTO_KWP2000_FAST = 5, + PROTO_CAN_11B_500K = 6, + PROTO_CAN_29B_500K = 7, + PROTO_CAN_29B_250K = 8, + PROTO_CAN_11B_250K = 9, +} OBD_PROTOCOLS; + +// states +typedef enum { + OBD_DISCONNECTED = 0, + OBD_CONNECTING = 1, + OBD_CONNECTED = 2, + OBD_FAILED = 3 +} OBD_STATES; + +uint16_t hex2uint16(const char *p); +uint8_t hex2uint8(const char *p); + +class COBD +{ +public: + COBD():dataMode(1),errors(0),m_state(OBD_DISCONNECTED) {} + // begin serial UART + virtual byte begin(); + // initialize OBD-II connection + virtual bool init(OBD_PROTOCOLS protocol = PROTO_AUTO); + // un-initialize OBD-II connection + virtual void end(); + // set serial baud rate + virtual bool setBaudRate(unsigned long baudrate); + // get connection state + virtual OBD_STATES getState() { return m_state; } + // read specified OBD-II PID value + virtual bool readPID(byte pid, int& result); + // read multiple (up to 8) OBD-II PID values, return number of values obtained + virtual byte readPID(const byte pid[], byte count, int result[]); + // set device into low power mode + virtual void enterLowPowerMode(); + // wake up device from low power mode + virtual void leaveLowPowerMode(); + // send AT command and receive response (return bytes received) + virtual byte sendCommand(const char* cmd, char* buf, byte bufsize, int timeout = OBD_TIMEOUT_LONG); + // read diagnostic trouble codes (return number of DTCs read) + virtual byte readDTC(uint16_t codes[], byte maxCodes = 1); + // clear diagnostic trouble code + virtual void clearDTC(); + // get battery voltage (works without ECU) + virtual float getVoltage(); + // get VIN as a string, buffer length should be >= OBD_RECV_BUF_SIZE + virtual bool getVIN(char* buffer, byte bufsize); + // initialize MEMS sensor + virtual bool memsInit(); + // read out MEMS data (acc for accelerometer, gyr for gyroscope, temp in 0.1 celcius degree) + virtual bool memsRead(int16_t* acc, int16_t* gyr = 0, int16_t* mag = 0, int16_t* temp = 0); + // send query for specified PID + virtual void sendQuery(byte pid); + // retrive and parse the response of specifie PID + virtual bool getResult(byte& pid, int& result); + // determine if the PID is supported + virtual bool isValidPID(byte pid); + // get adapter firmware version + virtual byte getVersion(); + // set current PID mode + byte dataMode; + // occurrence of errors + byte errors; + // bit map of supported PIDs + byte pidmap[4 * 4]; +protected: + virtual char* getResponse(byte& pid, char* buffer, byte bufsize); + virtual byte receive(char* buffer, byte bufsize, int timeout = OBD_TIMEOUT_SHORT); + virtual void write(const char* s); + virtual void dataIdleLoop() {} + void recover(); + void debugOutput(const char* s); + int normalizeData(byte pid, char* data); + OBD_STATES m_state; +private: + virtual uint8_t getPercentageValue(char* data) + { + return (uint16_t)hex2uint8(data) * 100 / 255; + } + virtual uint16_t getLargeValue(char* data) + { + return hex2uint16(data); + } + virtual uint8_t getSmallValue(char* data) + { + return hex2uint8(data); + } + virtual int16_t getTemperatureValue(char* data) + { + return (int)hex2uint8(data) - 40; + } + char* getResultValue(char* buf); +}; + diff --git a/libraries/OBD2UART/examples/obd_uart_test/obd_uart_test.ino b/libraries/OBD2UART/examples/obd_uart_test/obd_uart_test.ino new file mode 100755 index 0000000..9b11eab --- /dev/null +++ b/libraries/OBD2UART/examples/obd_uart_test/obd_uart_test.ino @@ -0,0 +1,188 @@ +/************************************************************************* +* Testing sketch for Freematics OBD-II UART Adapter +* Reads and prints several OBD-II PIDs value +* Distributed under GPL v2.0 +* Visit http://freematics.com for more information +* Written by Stanley Huang +*************************************************************************/ + +#include +#include + +// On Arduino Leonardo, Micro, MEGA or DUE, hardware serial can be used for output +// as OBD-II UART adapter uses to Serial1 +// On Arduino UNO and those have no Serial1, we use software serial for output +// as OBD-II UART adapter uses to Serial +SoftwareSerial mySerial(A2, A3); +//#define mySerial Serial + +COBD obd; +bool hasMEMS; + +void testOut() +{ + static const char cmds[][6] = {"ATZ\r", "ATI\r", "ATH0\r", "ATRV\r", "0100\r", "010C\r", "0902\r"}; + char buf[128]; + + for (byte i = 0; i < sizeof(cmds) / sizeof(cmds[0]); i++) { + const char *cmd = cmds[i]; + mySerial.print("Sending "); + mySerial.println(cmd); + if (obd.sendCommand(cmd, buf, sizeof(buf))) { + char *p = strstr(buf, cmd); + if (p) + p += strlen(cmd); + else + p = buf; + while (*p == '\r') p++; + while (*p) { + mySerial.write(*p); + if (*p == '\r' && *(p + 1) != '\r') + mySerial.write('\n'); + p++; + } + mySerial.println(); + } else { + mySerial.println("Timeout"); + } + delay(1000); + } + mySerial.println(); +} + +void readPIDSingle() +{ + int value; + mySerial.print('['); + mySerial.print(millis()); + mySerial.print(']'); + mySerial.print("RPM="); + if (obd.readPID(PID_RPM, value)) { + mySerial.print(value); + } + mySerial.println(); +} + +void readPIDMultiple() +{ + static const byte pids[] = {PID_SPEED, PID_ENGINE_LOAD, PID_THROTTLE, PID_COOLANT_TEMP}; + int values[sizeof(pids)]; + if (obd.readPID(pids, sizeof(pids), values) == sizeof(pids)) { + mySerial.print('['); + mySerial.print(millis()); + mySerial.print(']'); + for (byte i = 0; i < sizeof(pids) ; i++) { + mySerial.print((int)pids[i] | 0x100, HEX); + mySerial.print('='); + mySerial.print(values[i]); + mySerial.print(' '); + } + mySerial.println(); + } +} + +void readBatteryVoltage() +{ + mySerial.print('['); + mySerial.print(millis()); + mySerial.print(']'); + mySerial.print("Battery:"); + mySerial.print(obd.getVoltage(), 1); + mySerial.println('V'); +} + +void readMEMS() +{ + int acc[3]; + int gyro[3]; + int temp; + + if (!obd.memsRead(acc, gyro, 0, &temp)) return; + + mySerial.print('['); + mySerial.print(millis()); + mySerial.print(']'); + + mySerial.print("ACC:"); + mySerial.print(acc[0]); + mySerial.print('/'); + mySerial.print(acc[1]); + mySerial.print('/'); + mySerial.print(acc[2]); + + mySerial.print(" GYRO:"); + mySerial.print(gyro[0]); + mySerial.print('/'); + mySerial.print(gyro[1]); + mySerial.print('/'); + mySerial.print(gyro[2]); + + mySerial.print(" TEMP:"); + mySerial.print((float)temp / 10, 1); + mySerial.println("C"); +} + +void setup() +{ + mySerial.begin(115200); + while (!mySerial); + + // this will begin serial + byte version = obd.begin(); + + mySerial.print("Freematics OBD-II Adapter "); + if (version > 0) { + mySerial.print("Ver. "); + mySerial.print(version / 10); + mySerial.print('.'); + mySerial.println(version % 10); + } else { + mySerial.println("not detected"); + for (;;); + } + delay(1000); + + // send some commands for testing and show response for debugging purpose + testOut(); + + hasMEMS = obd.memsInit(); + mySerial.print("MEMS:"); + mySerial.println(hasMEMS ? "Yes" : "No"); + + // initialize OBD-II adapter + do { + mySerial.println("Init..."); + } while (!obd.init()); + + char buf[64]; + if (obd.getVIN(buf, sizeof(buf))) { + mySerial.print("VIN:"); + mySerial.println(buf); + } + + unsigned int codes[6]; + byte dtcCount = obd.readDTC(codes, 6); + if (dtcCount == 0) { + mySerial.println("No DTC"); + } else { + mySerial.print(dtcCount); + mySerial.print(" DTC:"); + for (byte n = 0; n < dtcCount; n++) { + mySerial.print(' '); + mySerial.print(codes[n], HEX); + } + mySerial.println(); + } + delay(5000); +} + + +void loop() +{ + readPIDSingle(); + readPIDMultiple(); + readBatteryVoltage(); + if (hasMEMS) { + readMEMS(); + } +} diff --git a/libraries/OBD2UART/examples/rpm_led_uart/rpm_led_uart.ino b/libraries/OBD2UART/examples/rpm_led_uart/rpm_led_uart.ino new file mode 100755 index 0000000..3e80ae3 --- /dev/null +++ b/libraries/OBD2UART/examples/rpm_led_uart/rpm_led_uart.ino @@ -0,0 +1,30 @@ +/************************************************************************* +* Sample sketch based on OBD-II library for Arduino +* Distributed under GPL v2.0 +* Visit http://freematics.com for more information +* (C)2012-2014 Stanley Huang +*************************************************************************/ + +#include + +COBD obd; + +void setup() +{ + // we'll use the debug LED as output + pinMode(13, OUTPUT); + // start communication with OBD-II UART adapter + obd.begin(); + // initiate OBD-II connection until success + while (!obd.init()); +} + +void loop() +{ + int value; + if (obd.readPID(PID_RPM, value)) { + // RPM is successfully read and its value stored in variable 'value' + // light on LED when RPM exceeds 3000 + digitalWrite(13, value > 3000 ? HIGH : LOW); + } +} diff --git a/libraries/SevSeg b/libraries/SevSeg new file mode 160000 index 0000000..46c3dfb --- /dev/null +++ b/libraries/SevSeg @@ -0,0 +1 @@ +Subproject commit 46c3dfb95f41e375cb2adb7e570c3d33aec89053 diff --git a/libraries/TimerOne b/libraries/TimerOne new file mode 160000 index 0000000..9e8f1af --- /dev/null +++ b/libraries/TimerOne @@ -0,0 +1 @@ +Subproject commit 9e8f1afc0af68fe2e9f82dcf4114d33600265225 diff --git a/libraries/narcoleptic b/libraries/narcoleptic new file mode 160000 index 0000000..f08b040 --- /dev/null +++ b/libraries/narcoleptic @@ -0,0 +1 @@ +Subproject commit f08b040a8ff144ba6d46d3e258c6dc8b840ab7d0 diff --git a/references/bttf1_screen_88_front.png b/references/bttf1_screen_88_front.png new file mode 100644 index 0000000..602788c Binary files /dev/null and b/references/bttf1_screen_88_front.png differ diff --git a/references/bttf1_screen_back.png b/references/bttf1_screen_back.png new file mode 100644 index 0000000..7d5b0a9 Binary files /dev/null and b/references/bttf1_screen_back.png differ diff --git a/references/gps_prop_1.jpg b/references/gps_prop_1.jpg new file mode 100644 index 0000000..099765c Binary files /dev/null and b/references/gps_prop_1.jpg differ diff --git a/references/gps_prop_2.jpg b/references/gps_prop_2.jpg new file mode 100644 index 0000000..dfa49ae Binary files /dev/null and b/references/gps_prop_2.jpg differ diff --git a/references/resources.md b/references/resources.md new file mode 100644 index 0000000..3b8e5d6 --- /dev/null +++ b/references/resources.md @@ -0,0 +1,36 @@ +*Mark D* on DMCTalk: + +> The digital speedo box is made from Hammond enclosure PN 1591CGY.  + +> The original was actually blue so production painted it white. The gray enclosure is close in color that you wouldn't need to paint it or you can still paint it satin white. The connector on the back is a D-sub type connector like you'd find on an old printer cable. There are various types you can buy on Newark.com, mouser.com, etc that are close, I don't have a source for any that are identical to the screen used connector. + +> The digital displays were originally made by Stanley Readout and are basically extinct at this point since they're ancient technology. Most people use 7 segment LED displays that look pretty accurate if you put a sheet of acetate film over the displays to act as a diffuser. I made a speedo a long time ago and used 1/16" styrene sheet to build the little black frame around each digit. The black border is part of the Stanley Readout on the real car, but you can make a close approximation of the real thing with polystyrene around an LED display and paint it black. + +> Here's a CAD model of how the original was constructed showing the Stanley Readout housings. If you're going for super accuracy people have made plastic castings of the stanley bodies and then made matching circuit boards with grain of wheat size incadecent bulbs or warm LED's. It also requires manufacturing a clear lens and as well.  + +![stanley readout model][stanley-explode] + +> The speedo was originally 3 digits and would read XX.X Apparently the third 1/10 MPH digit made reading the display confusing so production put a piece of black gaffer's tape over the 3rd digit so you only see XX.  + +> 1. The original box had areas where the paint was scratched through and you could see the blue plastic below. The paint was an off white/light gray color. + +> 2. The digits should sit flush with the outside of the case. + +> 3. Only a couple of Stanley readout displays have ever been found. The couple that still exist actually came from one of the production crew members that still had them from when the screen used cars were built. The bulbs are incandescent and run hot, so small LEDs are usually a better choice when making replicas. I don't have a ready source available for buying castings of the stanley housings. There is also a clear lens that clips over the black plastic housing that needs to be cast or cut from sheets of clear plastic and glued together. Making a mold for casting is difficult due to the shape and depth of the recesses in the part. The black housings may be a good candidate for 3D printing rather than molding. Creating reproduction circuit boards are also a complicating factor. +> A simpler solution is to use a readily available 7 segment LED displays and add plastic housings cut from sheet polystyrene to make them look more like the Stanley displays. + +> 4. The diffuser is a sheet of matte acetate, can be found at craft stores, amazon, staples, office max, etc. +https://www.amazon.com/Grafix-Acetat...YPMZ64EREHJ18Z + +> 5. The TCD labels were originally made with a special type of label maker and required black and red tape that would print white. Finding a vintage label maker and the correct size tape is probably not going to be viable option. +> A much better solution is to have custom vinyl decals printed and then cut them out to size. The Month, Day, Year, Hour, Min labels are .218" tall, the Destination Time, Present Time and Last Time Departed labels are .281" tall, and the AM PM labels are .188 tall. The labels were originally trimmed by hand to fit and are not all perfectly square or exactly the same height. The font is Microgramma Bold Extended + +> 6. The digital speedo was originally built with three digits and would have read 88.0 when the car hits 88 mph. This presumably proved to be difficult to read on screen and looked like "880" so production covered the tenths digit with a piece of black gaffer's tape. +https://www.amazon.com/3M-Cloth-Gaff.../dp/B00H95LOIG + +> The actual machine is a Kroy Model 80 label printer. It's 80's tech that's long since become obsolete so finding a working machine and the special printing tape in the right colors is an expensive (and time consuming) way to go. You also have to find the correct font printing wheel. A much better option is to print on vinyl and cut to size. + +[Source thread][dmc-talk-thread] + +[stanley-explode]: stanley_explode.png +[dmc-talk-thread]: http://dmctalk.org/showthread.php?11228-BTTF-Time-Machine-Build-Questions-Ask-Me-Anything&highlight=labels \ No newline at end of file diff --git a/references/stanley_explode.png b/references/stanley_explode.png new file mode 100644 index 0000000..bd67b42 Binary files /dev/null and b/references/stanley_explode.png differ