├── .gitignore ├── README.md ├── data └── RX-8 CAN bus data.xls ├── logger ├── Canbus.cpp ├── Canbus.h ├── README ├── crc8.cpp ├── crc8.h ├── defaults.h ├── global.h ├── logger.ino ├── mcp2515.c ├── mcp2515.h ├── mcp2515_defs.h ├── pc_interface.cpp └── pc_interface.h ├── python ├── arduino.py ├── can-dumper.py ├── console.py ├── debug-gaps.py ├── example_log.h5 ├── hdf5_log.py ├── monitor-gaps.py ├── plot_logs.py ├── rx8.py └── text_log.py └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.pyc 3 | *~ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | arduino-ecu-logger 2 | ================== 3 | 4 | Arduino + CAN-BUS shield to monitor fuel consumption and other vehicle parameters. I first wrote this to get a fuel economy meter on my RX-8, and later worked to reverse engineer the messages streaming across the CAN-BUS to see what sensor data is available on the car. 5 | 6 | Features: 7 | 8 | 1. Live streaming of CAN-BUS content over serial link to connected PC (with logging and viewing software on PC side) 9 | 2. Computes fuel consumption/mpg and displays on attached serial LCD 10 | 3. Dumps available OBD-II PIDs to microSD card 11 | 12 | ![screenshot](https://raw.githubusercontent.com/ihaque/arduino-ecu-logger/master/screenshot.png) 13 | 14 | **Table of Contents** 15 | 16 | - [arduino-ecu-logger](#) 17 | - [Materials](#) 18 | - [Arduino side](#) 19 | - [PC side](#) 20 | - [The RX-8 CAN](#) 21 | 22 | ## Materials 23 | 1. Arduino Uno 24 | 2. [Serial LCD] (https://www.sparkfun.com/products/9394) 25 | 3. [CAN-BUS shield](https://www.sparkfun.com/products/13262) (includes a joystick and microSD slot) 26 | 4. [OBD-II to DB9 cable](https://www.sparkfun.com/products/10087) to connect between your car and the CAN shield 27 | 5. microSD card 28 | 29 | ## Arduino side 30 | The Arduino can operate in one of four modes, selected on bootup using the joystick: 31 | 32 | 1. (down): live vehicle stats. Show MAF-based fuel efficiency (mpg) and consumption (oz/hr) on line 1 of LCD; coolant temperature and throttle position on line 2. 33 | 2. (up): CAN spy. Stream CAN-BUS frames over serial connection to attached PC for logging, reverse engineering, and analysis. 34 | 3. (left): query ECU for supported OBD-2 PIDs and write to microSD card. 35 | 4. (right): serial simulator. Send fake CAN-BUS frames over serial connection to test PC interface code. 36 | 37 | Hardware pin connections are described in logger/README. 38 | 39 | The PC interface uses a custom framing protocol for high-speed reliable transmission of CAN frames to the PC. Once every 127 frames, a synchronization frame is sent over the wire; each frame starts with a sentinel byte, and each frame is protected by a CRC8. 40 | 41 | ## PC side 42 | python/can-dumper.py supports reading CAN frames either from a serial-connected Arduino (python/arduino.py:ArduinoSource) or from an on-disk log (python/hdf5_log.py:HDF5Source), and can stream frames simultaneously to a number of outputs, including an on-disk log or a curses-based live display of different CAN-BUS addresses. A demo logfile is available to play with the viewer; run `python can-dumper.py example_log.h5`. 43 | 44 | The curses interface is shown below: 45 | ![screenshot](https://raw.githubusercontent.com/ihaque/arduino-ecu-logger/master/screenshot.png) 46 | 47 | The top two rows are a summary of the vehicle's current state, as inferred from decoding data on the CAN-BUS (see section below on the RX-8). Below that is a live-updating view of the last frame received for each CAN-BUS destination ID, including the `rtr` and `data` fields, as well as an estimate of the rate at which traffic is flowing to each ID. Following these fields as inputs are changed on a car (eg, throttle position, rpm, brake engagement, speed, steering angle) can help decode their meaning. 48 | 49 | ## The RX-8 CAN 50 | 51 | [This blog post](http://www.madox.net/blog/projects/mazda-can-bus/) describes some reverse engineering of CAN messages from a Mazda 3; much of the data is the same on my Mazda RX-8, but not all. The spreadsheet in data/ (as well as the decoding logic in python/rx8.py) describe the CAN IDs that I have successfully mapped on the RX-8. HDF5 logs can also be plotted using python/plot_logs.py. 52 | -------------------------------------------------------------------------------- /data/RX-8 CAN bus data.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihaque/arduino-ecu-logger/e303f19444163650b882aa9a8f86a9ada303dadc/data/RX-8 CAN bus data.xls -------------------------------------------------------------------------------- /logger/Canbus.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * 4 | * Copyright (c) 2008-2009 All rights reserved. 5 | */ 6 | 7 | #if ARDUINO>=100 8 | #include // Arduino 1.0 9 | #else 10 | #include // Arduino 0022 11 | #endif 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "pins_arduino.h" 20 | #include 21 | #include "global.h" 22 | #include "mcp2515.h" 23 | #include "defaults.h" 24 | #include "Canbus.h" 25 | 26 | 27 | 28 | 29 | /* C++ wrapper */ 30 | CanbusClass::CanbusClass() { 31 | 32 | 33 | } 34 | char CanbusClass::message_rx(unsigned char *buffer) { 35 | tCAN message; 36 | 37 | if (mcp2515_check_message()) { 38 | 39 | 40 | // Lese die Nachricht aus dem Puffern des MCP2515 41 | if (mcp2515_get_message(&message)) { 42 | // print_can_message(&message); 43 | // PRINT("\n"); 44 | buffer[0] = message.data[0]; 45 | buffer[1] = message.data[1]; 46 | buffer[2] = message.data[2]; 47 | buffer[3] = message.data[3]; 48 | buffer[4] = message.data[4]; 49 | buffer[5] = message.data[5]; 50 | buffer[6] = message.data[6]; 51 | buffer[7] = message.data[7]; 52 | // buffer[] = message[]; 53 | // buffer[] = message[]; 54 | // buffer[] = message[]; 55 | // buffer[] = message[]; 56 | } 57 | else { 58 | // PRINT("Kann die Nachricht nicht auslesen\n\n"); 59 | } 60 | } 61 | 62 | } 63 | 64 | char CanbusClass::message_tx(void) { 65 | tCAN message; 66 | 67 | 68 | // einige Testwerte 69 | message.id = 0x7DF; 70 | message.header.rtr = 0; 71 | message.header.length = 8; 72 | message.data[0] = 0x02; 73 | message.data[1] = 0x01; 74 | message.data[2] = 0x05; 75 | message.data[3] = 0x00; 76 | message.data[4] = 0x00; 77 | message.data[5] = 0x00; 78 | message.data[6] = 0x00; 79 | message.data[7] = 0x00; 80 | 81 | 82 | 83 | 84 | // mcp2515_bit_modify(CANCTRL, (1<>= 4; 111 | if (data.B & 0x02) u16_eng_data++; 112 | sprintf(buffer, "%u rpm", u16_eng_data); 113 | break; 114 | 115 | case ENGINE_COOLANT_TEMP: // A-40 degC 116 | sprintf(buffer, "%u degC", (unsigned)(data.A - 40U)); 117 | break; 118 | 119 | case VEHICLE_SPEED: // A kph 120 | sprintf(buffer, "%u kph", (unsigned) data.A); 121 | break; 122 | 123 | case MAF_SENSOR: //((256*A)+B)/100 g/s 124 | u16_eng_data = data.A; 125 | u16_eng_data = ((u16_eng_data << 8) | data.B) / 100U; 126 | sprintf(buffer, "%u g/s", u16_eng_data); 127 | break; 128 | 129 | case THROTTLE: 130 | case RELATIVE_THROTTLE: 131 | case CALC_ENGINE_LOAD: // A * 100 / 255 132 | u16_eng_data = data.A; 133 | u16_eng_data = (u16_eng_data * 100U) / 255U; 134 | sprintf(buffer, "%u %%", u16_eng_data); 135 | break; 136 | 137 | case PID_SUPPORT_01_20: 138 | case PID_SUPPORT_21_40: 139 | case PID_SUPPORT_41_60: 140 | case PID_SUPPORT_61_80: 141 | case PID_SUPPORT_81_A0: 142 | case PID_SUPPORT_A1_C0: 143 | case PID_SUPPORT_C1_E0: 144 | byte2hex(data.A, buffer); 145 | byte2hex(data.B, buffer+2); 146 | byte2hex(data.C, buffer+4); 147 | byte2hex(data.D, buffer+6); 148 | break; 149 | } 150 | return 1; 151 | } else { 152 | return 0; 153 | } 154 | } 155 | 156 | char CanbusClass::obd2_lowlevel(unsigned char mode, unsigned char* inbytes, 157 | unsigned char inlen, tOBD2 *data) 158 | { 159 | tCAN message; 160 | int timeout = 0; 161 | char message_ok = 0; 162 | 163 | // Validate message length 164 | if (inlen > 6) return 0; 165 | 166 | // Set up request message 167 | message.id = PID_REQUEST; 168 | message.header.rtr = 0; 169 | message.header.length = 8; 170 | message.data[0] = 1 + inlen; // OBD2 message bytes: mode + data 171 | message.data[1] = mode; 172 | memset(message.data + 2, 0, 6); 173 | if (inlen) { 174 | memcpy(message.data + 2, inbytes, inlen); 175 | } 176 | 177 | mcp2515_bit_modify(CANCTRL, (1<pid = message.data[2]; 188 | data->A = message.data[3]; 189 | data->B = message.data[4]; 190 | data->C = message.data[5]; 191 | data->D = message.data[6]; 192 | return 1; 193 | } 194 | } 195 | } 196 | return 0; 197 | 198 | } 199 | char CanbusClass::obd2_data(unsigned char pid, tOBD2 *data) 200 | { 201 | // Mode 1, request PID with no aux data. 202 | return obd2_lowlevel(0x01, &pid, 1, data); 203 | } 204 | 205 | char CanbusClass::init(unsigned char speed) { 206 | 207 | return mcp2515_init(speed); 208 | 209 | } 210 | 211 | CanbusClass Canbus; 212 | -------------------------------------------------------------------------------- /logger/Canbus.h: -------------------------------------------------------------------------------- 1 | /** 2 | * CAN BUS 3 | * 4 | * Copyright (c) 2010 Sukkin Pang All rights reserved. 5 | */ 6 | 7 | #ifndef canbus__h 8 | #define canbus__h 9 | 10 | #define CANSPEED_125 7 // CAN speed at 125 kbps 11 | #define CANSPEED_250 3 // CAN speed at 250 kbps 12 | #define CANSPEED_500 1 // CAN speed at 500 kbps 13 | 14 | 15 | #define CALC_ENGINE_LOAD 0x04 16 | #define ENGINE_COOLANT_TEMP 0x05 17 | #define ENGINE_RPM 0x0C 18 | #define VEHICLE_SPEED 0x0D 19 | #define MAF_SENSOR 0x10 20 | #define THROTTLE 0x11 21 | #define RELATIVE_THROTTLE 0x45 22 | #define O2_S1_WR_LAMBDA 0x34 23 | 24 | #define PID_SUPPORT_01_20 0x00 25 | #define PID_SUPPORT_21_40 0x20 26 | #define PID_SUPPORT_41_60 0x40 27 | #define PID_SUPPORT_61_80 0x60 28 | #define PID_SUPPORT_81_A0 0x80 29 | #define PID_SUPPORT_A1_C0 0xA0 30 | #define PID_SUPPORT_C1_E0 0xC0 31 | 32 | #define PID_REQUEST 0x7DF 33 | #define PID_REPLY 0x7E8 34 | 35 | static void byte2hex(unsigned char b, char* buf) { 36 | char lut[] = "0123456789ABCDEF"; 37 | buf[0] = lut[b >> 4]; 38 | buf[1] = lut[b & 0xF]; 39 | return; 40 | } 41 | 42 | typedef struct _tOBD2 43 | { 44 | unsigned char pid; 45 | unsigned char A; 46 | unsigned char B; 47 | unsigned char C; 48 | unsigned char D; 49 | } tOBD2; 50 | 51 | class CanbusClass 52 | { 53 | public: 54 | 55 | CanbusClass(); 56 | char init(unsigned char); 57 | char message_tx(void); 58 | char message_rx(unsigned char *buffer); 59 | char ecu_req(unsigned char pid, char *buffer); 60 | char obd2_data(unsigned char pid, tOBD2 *data); 61 | char obd2_lowlevel(unsigned char mode, unsigned char* inbytes, 62 | unsigned char inlen, tOBD2 *data); 63 | private: 64 | 65 | }; 66 | extern CanbusClass Canbus; 67 | //extern tCAN message; 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /logger/README: -------------------------------------------------------------------------------- 1 | # Arduino code for the ECU logger 2 | 3 | # Pin mappings 4 | * A0 - unused 5 | * A1 - CANshield up 6 | * A2 - CANshield right 7 | * A3 - CANshield down 8 | * A4 - CANshield click 9 | * A5 - CANshield left 10 | * 0 - HardwareSerial RX 11 | * 1 - HardwareSerial TX 12 | * 2 - MCP2515 interrupt 13 | * 3 - (unused) 14 | * 4 - (unused) 15 | * 5 - (unused) 16 | * 6 - CANshield LCD TX 17 | * 7 - (unused) CANshield LED1 18 | * 8 - (unused) CANshield LED2 19 | * 9 - CANshield SD CS 20 | * 10 - MCP2515 CS 21 | * 11 - SPI MOSI 22 | * 12 - SPI MISO 23 | * 13 - SPI SCK 24 | -------------------------------------------------------------------------------- /logger/crc8.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "crc8.h" 3 | // The CRC lookup table used for ATM HES (Polynomial = 0x07). 4 | // These are 256 unique 8-bit values. 5 | // From the BSD-licensed Chromium code. 6 | PROGMEM prog_uchar _atm_crc8_table[256] = { 7 | 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 8 | 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, 9 | 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 10 | 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, 11 | 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 12 | 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, 13 | 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 14 | 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, 15 | 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 16 | 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, 17 | 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 18 | 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, 19 | 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 20 | 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, 21 | 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 22 | 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, 23 | 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 24 | 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, 25 | 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 26 | 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, 27 | 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 28 | 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, 29 | 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 30 | 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, 31 | 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 32 | 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, 33 | 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 34 | 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, 35 | 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 36 | 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, 37 | 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 38 | 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 39 | }; 40 | 41 | uint8_t crc8(const uint8_t* buf, uint8_t length) { 42 | // The inital and final constants as used in the ATM HEC. 43 | const uint8_t initial = 0x00; 44 | const uint8_t final = 0x55; 45 | uint8_t crc = initial; 46 | while (length) { 47 | crc = pgm_read_byte_near(_atm_crc8_table + (*buf ^ crc)); 48 | buf++; 49 | length--; 50 | } 51 | return crc ^ final; 52 | } 53 | 54 | -------------------------------------------------------------------------------- /logger/crc8.h: -------------------------------------------------------------------------------- 1 | #ifndef _CRC8_H_ 2 | #define _CRC8_H_ 3 | uint8_t crc8(const uint8_t* buf, uint8_t length); 4 | #endif 5 | -------------------------------------------------------------------------------- /logger/defaults.h: -------------------------------------------------------------------------------- 1 | #ifndef DEFAULTS_H 2 | #define DEFAULTS_H 3 | 4 | // Pin mappings on Arduino ATMega328P 5 | // #define PIN_NAME Port register, register bit 6 | // These mappings translate Arduino-level pin names to port/bit pairs for 7 | // the C-level macros 8 | #define ARD_PIN_RST C,6 9 | #define ARD_PIN_0 D,0 10 | #define ARD_PIN_RX ARD_PIN_0 11 | #define ARD_PIN_1 D,1 12 | #define ARD_PIN_TX ARD_PIN_1 13 | #define ARD_PIN_2 D,2 14 | #define ARD_PIN_3 D,3 15 | #define ARD_PIN_4 D,4 16 | #define ARD_PIN_XTAL1 B,6 17 | #define ARD_PIN_XTAL2 B,7 18 | #define ARD_PIN_5 D,5 19 | #define ARD_PIN_6 D,6 20 | #define ARD_PIN_7 D,7 21 | #define ARD_PIN_8 B,0 22 | #define ARD_PIN_9 B,1 23 | #define ARD_PIN_10 B,2 24 | #define ARD_PIN_11 B,3 25 | #define ARD_PIN_12 B,4 26 | #define ARD_PIN_13 B,5 27 | #define ARD_PIN_A0 C,0 28 | #define ARD_PIN_A1 C,1 29 | #define ARD_PIN_A2 C,2 30 | #define ARD_PIN_A3 C,3 31 | #define ARD_PIN_A4 C,4 32 | #define ARD_PIN_A5 C,5 33 | 34 | #define P_MOSI ARD_PIN_11 35 | #define P_MISO ARD_PIN_12 36 | #define P_SCK ARD_PIN_13 37 | 38 | #define MCP2515_CS ARD_PIN_10 39 | #define MCP2515_INT ARD_PIN_2 40 | 41 | // CANBus shield accessory pins 42 | #define CAN_BUS_LCD_TX 6 43 | #define CAN_BUS_SD_CS 9 44 | 45 | // CANBus shield joystick pins 46 | #define UP A1 47 | #define RIGHT A2 48 | #define DOWN A3 49 | #define CLICK A4 50 | #define LEFT A5 51 | 52 | #endif // DEFAULTS_H 53 | -------------------------------------------------------------------------------- /logger/global.h: -------------------------------------------------------------------------------- 1 | #ifndef GLOBAL_H 2 | #define GLOBAL_H 3 | 4 | // ---------------------------------------------------------------------------- 5 | 6 | #define true 1 7 | #define false 0 8 | 9 | #define True 1 10 | #define False 0 11 | 12 | //typedef _Bool bool; 13 | //typedef boolean Bool; 14 | 15 | // ---------------------------------------------------------------------------- 16 | 17 | #define RESET(x) _XRS(x) 18 | #define SET(x) _XS(x) 19 | #define TOGGLE(x) _XT(x) 20 | #define SET_OUTPUT(x) _XSO(x) 21 | #define SET_INPUT(x) _XSI(x) 22 | #define IS_SET(x) _XR(x) 23 | 24 | #define PORT(x) _port2(x) 25 | #define DDR(x) _ddr2(x) 26 | #define PIN(x) _pin2(x) 27 | 28 | #define _XRS(x,y) PORT(x) &= ~(1< 2 | 3 | #include "defaults.h" 4 | #include "Canbus.h" 5 | #include "mcp2515.h" 6 | #include "pc_interface.h" 7 | extern CanbusClass Canbus; 8 | 9 | //#define FREE_MEMORY_MONITOR 10 | 11 | SoftwareSerial sLCD = SoftwareSerial(0, CAN_BUS_LCD_TX); 12 | #define COMMAND 0xFE 13 | #define CLEAR 0x01 14 | #define LINE0 0x80 15 | #define LINE1 0xC0 16 | 17 | // https://code.google.com/p/sdfatlib/ 18 | #ifdef _USE_SD_ 19 | #include 20 | #include 21 | SdFat _sdfat; 22 | #endif 23 | 24 | 25 | // TODO store error strings in flash to save RAM 26 | void error(const char* str) { 27 | Serial.print("error: "); 28 | Serial.println(str); 29 | 30 | clear_lcd(); 31 | sLCD.print(str); 32 | 33 | while(1); 34 | } 35 | 36 | void initJoy(void) { 37 | pinMode(UP, INPUT_PULLUP); 38 | pinMode(DOWN, INPUT_PULLUP); 39 | pinMode(LEFT, INPUT_PULLUP); 40 | pinMode(RIGHT, INPUT_PULLUP); 41 | pinMode(CLICK, INPUT_PULLUP); 42 | return; 43 | } 44 | 45 | void initSD() { 46 | #ifdef _USE_SD_ 47 | pinMode(CAN_BUS_SD_CS, OUTPUT); 48 | if (!_sdfat.begin(CAN_BUS_SD_CS)) { 49 | Serial.println("SD initialization failed!"); 50 | return; 51 | } 52 | Serial.println("SD initialization success"); 53 | #endif 54 | return; 55 | } 56 | 57 | void initLCD(unsigned int baud) { 58 | byte speed_control; 59 | switch (baud) { 60 | case 2400U: speed_control = 0x0B; break; 61 | case 4800U: speed_control = 0x0C; break; 62 | case 9600U: speed_control = 0x0D; break; 63 | case 14400U: speed_control = 0x0E; break; 64 | case 19200U: speed_control = 0x0F; break; 65 | case 38400U: speed_control = 0x10; break; 66 | default: baud = 9600U; speed_control = 0x0D; break; 67 | } 68 | 69 | sLCD.begin(9600U); 70 | sLCD.write(0x7C); 71 | sLCD.write(speed_control); 72 | delay(50); // Delay, or else LCD goes blank 73 | sLCD.begin(baud); 74 | return; 75 | } 76 | 77 | void initCAN() { 78 | // Initialize MCP2515 CAN controller at the specified speed 79 | if(Canbus.init(CANSPEED_500)) 80 | sLCD.print("CAN Init ok"); 81 | else { 82 | sLCD.print("Can't init CAN"); 83 | while (1); 84 | } 85 | delay(500); 86 | } 87 | 88 | void simulate_serial_dump(void) { 89 | tCAN msg; 90 | msg.id = 0x0001; 91 | msg.header.rtr = 0; 92 | msg.header.length = 8; 93 | msg.data[0] = 0x80; 94 | msg.data[1] = 0x08; 95 | msg.data[2] = 0x50; 96 | msg.data[3] = 0x00; 97 | msg.data[4] = 0xDE; 98 | msg.data[5] = 0xAD; 99 | msg.data[6] = 0xBE; 100 | msg.data[7] = 0xEF; 101 | clear_lcd(); 102 | sLCD.write(COMMAND); 103 | sLCD.write(LINE0); 104 | sLCD.write("Simulating..."); 105 | while (1) { 106 | upload_CAN_message(&msg); 107 | msg.id = (msg.id * 907) % 23; 108 | msg.data[3]++; 109 | //delay(5); 110 | } 111 | } 112 | 113 | void setup() { 114 | Serial.begin(115200); 115 | initJoy(); 116 | initSD(); 117 | initLCD(9600); 118 | clear_lcd(); 119 | initCAN(); 120 | 121 | Serial.println("ECU Reader"); /* For debug use */ 122 | 123 | clear_lcd(); 124 | 125 | sLCD.print("D:CAN U:SPY"); 126 | sLCD.write(COMMAND); 127 | sLCD.write(LINE1); 128 | sLCD.print("L:PIDs R:SIM"); 129 | 130 | while(1) 131 | { 132 | if (digitalRead(UP) == 0){ 133 | Serial.println("can spy"); 134 | sLCD.print("SPY"); 135 | can_spy(); 136 | } 137 | if (digitalRead(DOWN) == 0) { 138 | sLCD.print("CAN"); 139 | Serial.println("CAN"); 140 | break; 141 | } 142 | if (digitalRead(LEFT) == 0) { 143 | Serial.println("PID dumper"); 144 | dump_pids(); 145 | } 146 | if (digitalRead(RIGHT) == 0) { 147 | Serial.println("Simulator"); 148 | simulate_serial_dump(); 149 | } 150 | } 151 | clear_lcd(); 152 | } 153 | 154 | void loop() { 155 | char buffer[17]; 156 | float mpg = compute_mpg(); 157 | float oz_per_hr = consumption_ozph(); 158 | sprintf(buffer,"%3u mpg %3u oz/h", (unsigned) mpg, (unsigned) oz_per_hr); 159 | sLCD.write(COMMAND); 160 | sLCD.write(LINE0); 161 | sLCD.write(buffer); 162 | 163 | //if(Canbus.ecu_req(CALC_ENGINE_LOAD,buffer) == 1) { 164 | // sLCD.write(COMMAND); 165 | // sLCD.write(LINE0 + 9); 166 | // sLCD.print(buffer); 167 | //} 168 | 169 | if(Canbus.ecu_req(ENGINE_COOLANT_TEMP,buffer) == 1) { 170 | sLCD.write(COMMAND); 171 | sLCD.write(LINE1); 172 | sLCD.print(buffer); 173 | } 174 | 175 | if(Canbus.ecu_req(RELATIVE_THROTTLE,buffer) == 1) { 176 | sLCD.write(COMMAND); 177 | sLCD.write(LINE1 + 9); 178 | sLCD.print(buffer); 179 | } 180 | 181 | delay(100); 182 | } 183 | 184 | void logging(void) { 185 | char buffer[17]; 186 | clear_lcd(); 187 | sLCD.print("Press J/S click"); 188 | sLCD.write(COMMAND); 189 | sLCD.write(LINE1); /* Move LCD cursor to line 1 */ 190 | sLCD.print("to Stop"); 191 | 192 | while(1) /* Main logging loop */ 193 | { 194 | if(Canbus.ecu_req(ENGINE_RPM,buffer) == 1) { 195 | sLCD.write(COMMAND); 196 | sLCD.write(LINE0); 197 | sLCD.print(buffer); 198 | // file.print(buffer); 199 | // file.print(','); 200 | } 201 | 202 | if(Canbus.ecu_req(VEHICLE_SPEED,buffer) == 1) { 203 | sLCD.write(COMMAND); 204 | sLCD.write(LINE0 + 9); 205 | sLCD.print(buffer); 206 | //file.print(buffer); 207 | //file.print(','); 208 | } 209 | 210 | if(Canbus.ecu_req(ENGINE_COOLANT_TEMP,buffer) == 1) { 211 | sLCD.write(COMMAND); 212 | sLCD.write(LINE1); 213 | sLCD.print(buffer); 214 | // file.print(buffer); 215 | } 216 | 217 | if(Canbus.ecu_req(THROTTLE,buffer) == 1) { 218 | sLCD.write(COMMAND); 219 | sLCD.write(LINE1 + 9); 220 | sLCD.print(buffer); 221 | // file.print(buffer); 222 | } 223 | 224 | if (digitalRead(CLICK) == 0) { 225 | //file.close(); 226 | Serial.println("Done"); 227 | sLCD.write(COMMAND); 228 | sLCD.write(CLEAR); 229 | 230 | sLCD.print("DONE"); 231 | while(1); 232 | } 233 | } 234 | } 235 | 236 | void dump_pids(void) 237 | { 238 | #ifndef _USE_SD_ 239 | sLCD.write("Compiled without SD support"); 240 | #else 241 | char buffer[17]; 242 | clear_lcd(); 243 | sLCD.print("SD test"); 244 | SdFile fp; 245 | fp.open("SDtest.txt", O_WRITE | O_TRUNC); 246 | fp.write("Hello, world!"); 247 | fp.close(); 248 | char buf[9*7 + 1]; 249 | buf[9*7] = 0; 250 | for (byte row = 0; row < 7; row++) { 251 | for (byte col = 0; col < 8; col++) { 252 | buf[row*9 + col] = '-'; 253 | } 254 | buf[row*9 + 8] = '\n'; 255 | } 256 | byte CODES[7] = {PID_SUPPORT_01_20, 257 | PID_SUPPORT_21_40, 258 | PID_SUPPORT_41_60, 259 | PID_SUPPORT_61_80, 260 | PID_SUPPORT_81_A0, 261 | PID_SUPPORT_A1_C0, 262 | PID_SUPPORT_C1_E0}; 263 | 264 | clear_lcd(); 265 | sLCD.write(COMMAND); 266 | sLCD.write(LINE0); 267 | sLCD.write("Reading..."); 268 | sLCD.write(COMMAND); 269 | sLCD.write(LINE1); 270 | fp.open("PIDS_SUP.RAW", O_WRITE | O_APPEND); 271 | fp.write("\n"); 272 | fp.write("Starting new read\n"); 273 | fp.sync(); 274 | for (int i = 0; i < 7; i++) { 275 | if (!Canbus.ecu_req(CODES[i], buf + 9*i)) { 276 | sLCD.write("Done."); 277 | break; 278 | } else { 279 | sprintf(buffer,"%d",i); 280 | sLCD.write(buffer); 281 | } 282 | fp.write("\n\r"); 283 | fp.write(buf); 284 | fp.sync(); 285 | } 286 | fp.close(); 287 | #endif 288 | while(1); /* Don't return */ 289 | } 290 | 291 | bool get_CAN_msg(tCAN *pmsg, unsigned timeout_ms) { 292 | unsigned long start = millis(); 293 | while (!mcp2515_check_message()) { 294 | if (timeout_ms && (millis() - start) > timeout_ms) return false; 295 | } 296 | mcp2515_get_message(pmsg); 297 | return true; 298 | } 299 | 300 | void can_spy(void) { 301 | tCAN msg; 302 | uint16_t consec_fails = 0; 303 | while (true) { 304 | if (get_CAN_msg(&msg, 10)) { 305 | consec_fails = 0; 306 | } else { 307 | // No frames received in 10ms. Dump a logging frame. 308 | uint8_t read_status = mcp2515_read_status(SPI_READ_STATUS); 309 | uint8_t rx_status = mcp2515_read_status(SPI_RX_STATUS); 310 | consec_fails++; 311 | msg.id = 0xFFFF; 312 | msg.header.rtr = 0; 313 | msg.header.length = 4; 314 | msg.data[0] = read_status; 315 | msg.data[1] = rx_status; 316 | msg.data[2] = consec_fails >> 8; 317 | msg.data[3] = consec_fails & 0xFF; 318 | } 319 | upload_CAN_message(&msg); 320 | } 321 | } 322 | 323 | float compute_mpg(void) { 324 | /* VEHICLE_SPEED gives us speed in kph 325 | * MAF_SENSOR / 100 gives us gram/s air intake 326 | * O2_LAMBDA / 2^15 gives us ratio by which we are lean of stoich 327 | * 328 | * g air/s * 1/14.7 g gas/g air / lambda = g gas/sec 329 | * g gas/s * 1 lb/454 g * 1 gal/6.701 lb * 3600 s/hr = gal gas/hr 330 | * speed kph * .621317 = speed mph 331 | * speed mph / gal gas/hr = mi/gal 332 | * 333 | * SPEED * 0.621317 * 14.7 * 454 * 6.701 * LAMBDA / (MAF * 3600) = mpg 334 | * SPEED * 27786 * LAMBDA / (MAF * 3600) = mpg 335 | * SPEED * 7.71833 * LAMBDA / (MAF) = mpg 336 | * 337 | * MAF_SENSOR = MAF * 100 338 | * O2_LAMBDA = LAMBDA * 32768 339 | * 340 | * SPEED * 7.71833 * O2_LAMBDA / 32768 / (MAF_SENSOR / 100) = mpg 341 | * SPEED * 7.71833 * O2_LAMBDA * 100 / (MAF_SENSOR * 32768) = mpg 342 | * SPEED * O2_LAMBDA / (MAF_SENSOR * 42.45478) = mpg 343 | */ 344 | tOBD2 data; 345 | byte speed; 346 | unsigned maf_times_100, lambda_times_32k; 347 | float mpg; 348 | Canbus.obd2_data(VEHICLE_SPEED, &data); 349 | speed = data.A; 350 | Canbus.obd2_data(MAF_SENSOR, &data); 351 | maf_times_100 = ((unsigned)data.A << 8) | data.B; 352 | Canbus.obd2_data(O2_S1_WR_LAMBDA, &data); 353 | lambda_times_32k = ((unsigned)data.A << 8) | data.B; 354 | mpg = (float(speed) * lambda_times_32k) / (maf_times_100 * 42.45478); 355 | if (speed == 0) return 0.0f; 356 | else return mpg; 357 | } 358 | 359 | float consumption_ozph(void) { 360 | /* MAF_SENSOR / 100 gives us gram/s air intake 361 | * O2_S1_WR_LAMBDA / 2^15 gives us ratio by which we are lean of stoich 362 | * 363 | * g air/s * 1/14.7 g gas/g air / lambda = g gas/sec 364 | * g gas/s * 1 lb/454 g * 1 gal/6.701 lb * 3600 s/hr * 128 oz/gal = oz gas/hr 365 | * 366 | * (MAF_SENSOR / 100) * (1/14.7) / (O2_LAMBDA / 32768) * (1/454) * (1/6.701) * 3600 * 128 367 | * MAF_SENSOR * 32768 * 3600 * 128 / (100 * 14.7 * O2_LAMBDA * 454 * 6.701) 368 | * 369 | * MAF_SENSOR * 3376.36 / O2_LAMBDA = oz gas/hr 370 | */ 371 | tOBD2 data; 372 | unsigned maf_times_100, lambda_times_32k; 373 | float oz_per_hr; 374 | Canbus.obd2_data(MAF_SENSOR, &data); 375 | maf_times_100 = ((unsigned)data.A << 8) | data.B; 376 | Canbus.obd2_data(O2_S1_WR_LAMBDA, &data); 377 | lambda_times_32k = ((unsigned)data.A << 8) | data.B; 378 | oz_per_hr = (maf_times_100 * 3376.36f) / lambda_times_32k; 379 | Serial.print("MAF = "); 380 | Serial.println(float(maf_times_100) / 100.0f); 381 | Serial.print("O2 LAMBDA ="); 382 | Serial.println(float(lambda_times_32k) / 32768.0f); 383 | Serial.print("\n"); 384 | return oz_per_hr; 385 | } 386 | 387 | void clear_lcd(void) 388 | { 389 | sLCD.write(COMMAND); 390 | sLCD.write(CLEAR); 391 | } 392 | 393 | #ifdef FREE_MEMORY_MONITOR 394 | extern unsigned int __heap_start; 395 | extern void *__brkval; 396 | 397 | /* 398 | * The free list structure as maintained by the 399 | * avr-libc memory allocation routines. 400 | */ 401 | struct __freelist { 402 | size_t sz; 403 | struct __freelist *nx; 404 | }; 405 | 406 | /* The head of the free list structure */ 407 | extern struct __freelist *__flp; 408 | 409 | /* Calculates the size of the free list */ 410 | int freeListSize() { 411 | struct __freelist* current; 412 | int total = 0; 413 | 414 | for (current = __flp; current; current = current->nx) { 415 | total += 2; /* Add two bytes for the memory block's header */ 416 | total += (int) current->sz; 417 | } 418 | 419 | return total; 420 | } 421 | 422 | int freeMemory() { 423 | int free_memory; 424 | 425 | if ((int)__brkval == 0) { 426 | free_memory = ((int)&free_memory) - ((int)&__heap_start); 427 | } else { 428 | free_memory = ((int)&free_memory) - ((int)__brkval); 429 | free_memory += freeListSize(); 430 | } 431 | sLCD.write(COMMAND); 432 | sLCD.write(LINE0); 433 | char _mbuf[12]; 434 | sprintf(_mbuf, "%d b free", free_memory); 435 | sLCD.write(_mbuf); 436 | sLCD.write(COMMAND); 437 | sLCD.write(LINE1); 438 | sprintf(_mbuf, "%d b tCAN", sizeof(tCAN)); 439 | sLCD.write(_mbuf); 440 | delay(1000); 441 | return free_memory; 442 | } 443 | #endif 444 | -------------------------------------------------------------------------------- /logger/mcp2515.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2007 Fabian Greif 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | // ---------------------------------------------------------------------------- 27 | 28 | 29 | #include 30 | #include 31 | 32 | #if ARDUINO>=100 33 | #include // Arduino 1.0 34 | #else 35 | #include // Arduino 0022 36 | #endif 37 | #include 38 | #include 39 | 40 | #include "global.h" 41 | #include "mcp2515.h" 42 | #include "mcp2515_defs.h" 43 | 44 | 45 | #include "defaults.h" 46 | 47 | // ------------------------------------------------------------------------- 48 | // Schreibt/liest ein Byte ueber den Hardware SPI Bus 49 | 50 | uint8_t spi_putc( uint8_t data ) 51 | { 52 | // put byte in send-buffer 53 | SPDR = data; 54 | 55 | // wait until byte was send 56 | while( !( SPSR & (1< is the chip accessible? 166 | if (mcp2515_read_register(CNF1) != speed) { 167 | return false; 168 | } 169 | 170 | // deaktivate the RXnBF Pins (High Impedance State) 171 | mcp2515_write_register(BFPCTRL, 0); 172 | 173 | // set TXnRTS as inputs 174 | mcp2515_write_register(TXRTSCTRL, 0); 175 | 176 | // turn off filters => receive any message 177 | mcp2515_write_register(RXB0CTRL, (1<id = (uint16_t) spi_putc(0xff) << 3; 232 | message->id |= spi_putc(0xff) >> 5; 233 | 234 | spi_putc(0xff); 235 | spi_putc(0xff); 236 | 237 | // read DLC 238 | uint8_t length = spi_putc(0xff) & 0x0f; 239 | 240 | message->header.length = length; 241 | message->header.rtr = (bit_is_set(status, 3)) ? 1 : 0; 242 | 243 | // read data 244 | for (t=0;tdata[t] = spi_putc(0xff); 246 | } 247 | SET(MCP2515_CS); 248 | 249 | // READ_RX automatically clears interrupt flag on CS so we don't need to 250 | 251 | return (status & 0x07) + 1; 252 | } 253 | 254 | // ---------------------------------------------------------------------------- 255 | uint8_t mcp2515_send_message(tCAN *message) 256 | { 257 | uint8_t status = mcp2515_read_status(SPI_READ_STATUS); 258 | 259 | /* Statusbyte: 260 | * 261 | * Bit Function 262 | * 2 TXB0CNTRL.TXREQ 263 | * 4 TXB1CNTRL.TXREQ 264 | * 6 TXB2CNTRL.TXREQ 265 | */ 266 | uint8_t address; 267 | uint8_t t; 268 | if (bit_is_clear(status, 2)) { 269 | address = 0x00; 270 | } 271 | else if (bit_is_clear(status, 4)) { 272 | address = 0x02; 273 | } 274 | else if (bit_is_clear(status, 6)) { 275 | address = 0x04; 276 | } 277 | else { 278 | // all buffer used => could not send message 279 | return 0; 280 | } 281 | 282 | RESET(MCP2515_CS); 283 | spi_putc(SPI_WRITE_TX | address); 284 | 285 | spi_putc(message->id >> 3); 286 | spi_putc(message->id << 5); 287 | 288 | spi_putc(0); 289 | spi_putc(0); 290 | 291 | uint8_t length = message->header.length & 0x0f; 292 | 293 | if (message->header.rtr) { 294 | // a rtr-frame has a length, but contains no data 295 | spi_putc((1<data[t]); 304 | } 305 | } 306 | SET(MCP2515_CS); 307 | 308 | _delay_us(1); 309 | 310 | // send message 311 | RESET(MCP2515_CS); 312 | address = (address == 0) ? 1 : address; 313 | spi_putc(SPI_RTS | address); 314 | SET(MCP2515_CS); 315 | 316 | return address; 317 | } 318 | -------------------------------------------------------------------------------- /logger/mcp2515.h: -------------------------------------------------------------------------------- 1 | #ifndef MCP2515_H 2 | #define MCP2515_H 3 | 4 | // ---------------------------------------------------------------------------- 5 | /* Copyright (c) 2007 Fabian Greif 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * 1. Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in the 16 | * documentation and/or other materials provided with the distribution. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND 19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 | * SUCH DAMAGE. 29 | */ 30 | // ---------------------------------------------------------------------------- 31 | 32 | #include 33 | 34 | #include "mcp2515_defs.h" 35 | #include "global.h" 36 | #ifdef __cplusplus 37 | 38 | extern "C" 39 | { 40 | 41 | 42 | #endif 43 | // ---------------------------------------------------------------------------- 44 | typedef struct 45 | { 46 | uint16_t id; 47 | struct { 48 | int8_t rtr : 1; 49 | uint8_t length : 4; 50 | } header; 51 | uint8_t data[8]; 52 | } tCAN; 53 | 54 | // ---------------------------------------------------------------------------- 55 | uint8_t spi_putc( uint8_t data ); 56 | 57 | // ---------------------------------------------------------------------------- 58 | void mcp2515_write_register( uint8_t adress, uint8_t data ); 59 | 60 | // ---------------------------------------------------------------------------- 61 | uint8_t mcp2515_read_register(uint8_t adress); 62 | 63 | // ---------------------------------------------------------------------------- 64 | void mcp2515_bit_modify(uint8_t adress, uint8_t mask, uint8_t data); 65 | 66 | // ---------------------------------------------------------------------------- 67 | uint8_t mcp2515_read_status(uint8_t type); 68 | 69 | // ---------------------------------------------------------------------------- 70 | 71 | uint8_t mcp2515_init(uint8_t speed); 72 | 73 | // ---------------------------------------------------------------------------- 74 | // check if there are any new messages waiting 75 | uint8_t mcp2515_check_message(void); 76 | 77 | // ---------------------------------------------------------------------------- 78 | // check if there is a free buffer to send messages 79 | uint8_t mcp2515_check_free_buffer(void); 80 | 81 | // ---------------------------------------------------------------------------- 82 | uint8_t mcp2515_get_message(tCAN *message); 83 | 84 | // ---------------------------------------------------------------------------- 85 | uint8_t mcp2515_send_message(tCAN *message); 86 | 87 | 88 | #ifdef __cplusplus 89 | } 90 | #endif 91 | 92 | #endif // MCP2515_H 93 | -------------------------------------------------------------------------------- /logger/mcp2515_defs.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef MCP2515_DEFS_H 3 | #define MCP2515_DEFS_H 4 | 5 | /** \name SPI Kommandos */ 6 | /*@{*/ 7 | #define SPI_RESET 0xC0 8 | #define SPI_READ 0x03 9 | #define SPI_READ_RX 0x90 10 | #define SPI_WRITE 0x02 11 | #define SPI_WRITE_TX 0x40 12 | #define SPI_RTS 0x80 13 | #define SPI_READ_STATUS 0xA0 14 | #define SPI_RX_STATUS 0xB0 15 | #define SPI_BIT_MODIFY 0x05 16 | /*@}*/ 17 | 18 | /** \name Adressen der Register des MCP2515 19 | * 20 | * Die Redundanten Adressen von z.B. dem Register CANSTAT 21 | * (0x0E, 0x1E, 0x2E, ...) wurden dabei nicht mit aufgelistet. 22 | */ 23 | /*@{*/ 24 | #define RXF0SIDH 0x00 25 | #define RXF0SIDL 0x01 26 | #define RXF0EID8 0x02 27 | #define RXF0EID0 0x03 28 | #define RXF1SIDH 0x04 29 | #define RXF1SIDL 0x05 30 | #define RXF1EID8 0x06 31 | #define RXF1EID0 0x07 32 | #define RXF2SIDH 0x08 33 | #define RXF2SIDL 0x09 34 | #define RXF2EID8 0x0A 35 | #define RXF2EID0 0x0B 36 | #define BFPCTRL 0x0C 37 | #define TXRTSCTRL 0x0D 38 | #define CANSTAT 0x0E 39 | #define CANCTRL 0x0F 40 | 41 | #define RXF3SIDH 0x10 42 | #define RXF3SIDL 0x11 43 | #define RXF3EID8 0x12 44 | #define RXF3EID0 0x13 45 | #define RXF4SIDH 0x14 46 | #define RXF4SIDL 0x15 47 | #define RXF4EID8 0x16 48 | #define RXF4EID0 0x17 49 | #define RXF5SIDH 0x18 50 | #define RXF5SIDL 0x19 51 | #define RXF5EID8 0x1A 52 | #define RXF5EID0 0x1B 53 | #define TEC 0x1C 54 | #define REC 0x1D 55 | 56 | #define RXM0SIDH 0x20 57 | #define RXM0SIDL 0x21 58 | #define RXM0EID8 0x22 59 | #define RXM0EID0 0x23 60 | #define RXM1SIDH 0x24 61 | #define RXM1SIDL 0x25 62 | #define RXM1EID8 0x26 63 | #define RXM1EID0 0x27 64 | #define CNF3 0x28 65 | #define CNF2 0x29 66 | #define CNF1 0x2A 67 | #define CANINTE 0x2B 68 | #define CANINTF 0x2C 69 | #define EFLG 0x2D 70 | 71 | #define TXB0CTRL 0x30 72 | #define TXB0SIDH 0x31 73 | #define TXB0SIDL 0x32 74 | #define TXB0EID8 0x33 75 | #define TXB0EID0 0x34 76 | #define TXB0DLC 0x35 77 | #define TXB0D0 0x36 78 | #define TXB0D1 0x37 79 | #define TXB0D2 0x38 80 | #define TXB0D3 0x39 81 | #define TXB0D4 0x3A 82 | #define TXB0D5 0x3B 83 | #define TXB0D6 0x3C 84 | #define TXB0D7 0x3D 85 | 86 | #define TXB1CTRL 0x40 87 | #define TXB1SIDH 0x41 88 | #define TXB1SIDL 0x42 89 | #define TXB1EID8 0x43 90 | #define TXB1EID0 0x44 91 | #define TXB1DLC 0x45 92 | #define TXB1D0 0x46 93 | #define TXB1D1 0x47 94 | #define TXB1D2 0x48 95 | #define TXB1D3 0x49 96 | #define TXB1D4 0x4A 97 | #define TXB1D5 0x4B 98 | #define TXB1D6 0x4C 99 | #define TXB1D7 0x4D 100 | 101 | #define TXB2CTRL 0x50 102 | #define TXB2SIDH 0x51 103 | #define TXB2SIDL 0x52 104 | #define TXB2EID8 0x53 105 | #define TXB2EID0 0x54 106 | #define TXB2DLC 0x55 107 | #define TXB2D0 0x56 108 | #define TXB2D1 0x57 109 | #define TXB2D2 0x58 110 | #define TXB2D3 0x59 111 | #define TXB2D4 0x5A 112 | #define TXB2D5 0x5B 113 | #define TXB2D6 0x5C 114 | #define TXB2D7 0x5D 115 | 116 | #define RXB0CTRL 0x60 117 | #define RXB0SIDH 0x61 118 | #define RXB0SIDL 0x62 119 | #define RXB0EID8 0x63 120 | #define RXB0EID0 0x64 121 | #define RXB0DLC 0x65 122 | #define RXB0D0 0x66 123 | #define RXB0D1 0x67 124 | #define RXB0D2 0x68 125 | #define RXB0D3 0x69 126 | #define RXB0D4 0x6A 127 | #define RXB0D5 0x6B 128 | #define RXB0D6 0x6C 129 | #define RXB0D7 0x6D 130 | 131 | #define RXB1CTRL 0x70 132 | #define RXB1SIDH 0x71 133 | #define RXB1SIDL 0x72 134 | #define RXB1EID8 0x73 135 | #define RXB1EID0 0x74 136 | #define RXB1DLC 0x75 137 | #define RXB1D0 0x76 138 | #define RXB1D1 0x77 139 | #define RXB1D2 0x78 140 | #define RXB1D3 0x79 141 | #define RXB1D4 0x7A 142 | #define RXB1D5 0x7B 143 | #define RXB1D6 0x7C 144 | #define RXB1D7 0x7D 145 | /*@}*/ 146 | 147 | /** \name Bitdefinition der verschiedenen Register */ 148 | /*@{*/ 149 | 150 | /** \brief Bitdefinition von BFPCTRL */ 151 | #define B1BFS 5 152 | #define B0BFS 4 153 | #define B1BFE 3 154 | #define B0BFE 2 155 | #define B1BFM 1 156 | #define B0BFM 0 157 | 158 | /** \brief Bitdefinition von TXRTSCTRL */ 159 | #define B2RTS 5 160 | #define B1RTS 4 161 | #define B0RTS 3 162 | #define B2RTSM 2 163 | #define B1RTSM 1 164 | #define B0RTSM 0 165 | 166 | /** \brief Bitdefinition von CANSTAT */ 167 | #define OPMOD2 7 168 | #define OPMOD1 6 169 | #define OPMOD0 5 170 | #define ICOD2 3 171 | #define ICOD1 2 172 | #define ICOD0 1 173 | 174 | /** \brief Bitdefinition von CANCTRL */ 175 | #define REQOP2 7 176 | #define REQOP1 6 177 | #define REQOP0 5 178 | #define ABAT 4 179 | #define CLKEN 2 180 | #define CLKPRE1 1 181 | #define CLKPRE0 0 182 | 183 | /** \brief Bitdefinition von CNF3 */ 184 | #define WAKFIL 6 185 | #define PHSEG22 2 186 | #define PHSEG21 1 187 | #define PHSEG20 0 188 | 189 | /** \brief Bitdefinition von CNF2 */ 190 | #define BTLMODE 7 191 | #define SAM 6 192 | #define PHSEG12 5 193 | #define PHSEG11 4 194 | #define PHSEG10 3 195 | #define PHSEG2 2 196 | #define PHSEG1 1 197 | #define PHSEG0 0 198 | 199 | /** \brief Bitdefinition von CNF1 */ 200 | #define SJW1 7 201 | #define SJW0 6 202 | #define BRP5 5 203 | #define BRP4 4 204 | #define BRP3 3 205 | #define BRP2 2 206 | #define BRP1 1 207 | #define BRP0 0 208 | 209 | /** \brief Bitdefinition von CANINTE */ 210 | #define MERRE 7 211 | #define WAKIE 6 212 | #define ERRIE 5 213 | #define TX2IE 4 214 | #define TX1IE 3 215 | #define TX0IE 2 216 | #define RX1IE 1 217 | #define RX0IE 0 218 | 219 | /** \brief Bitdefinition von CANINTF */ 220 | #define MERRF 7 221 | #define WAKIF 6 222 | #define ERRIF 5 223 | #define TX2IF 4 224 | #define TX1IF 3 225 | #define TX0IF 2 226 | #define RX1IF 1 227 | #define RX0IF 0 228 | 229 | /** \brief Bitdefinition von EFLG */ 230 | #define RX1OVR 7 231 | #define RX0OVR 6 232 | #define TXB0 5 233 | #define TXEP 4 234 | #define RXEP 3 235 | #define TXWAR 2 236 | #define RXWAR 1 237 | #define EWARN 0 238 | 239 | /** \brief Bitdefinition von TXBnCTRL (n = 0, 1, 2) */ 240 | #define ABTF 6 241 | #define MLOA 5 242 | #define TXERR 4 243 | #define TXREQ 3 244 | #define TXP1 1 245 | #define TXP0 0 246 | 247 | /** \brief Bitdefinition von RXB0CTRL */ 248 | #define RXM1 6 249 | #define RXM0 5 250 | #define RXRTR 3 251 | #define BUKT 2 252 | #define BUKT1 1 253 | #define FILHIT0 0 254 | 255 | /** \brief Bitdefinition von TXBnSIDL (n = 0, 1) */ 256 | #define EXIDE 3 257 | 258 | /** 259 | * \brief Bitdefinition von RXB1CTRL 260 | * \see RXM1, RXM0, RXRTR und FILHIT0 sind schon fuer RXB0CTRL definiert 261 | */ 262 | #define FILHIT2 2 263 | #define FILHIT1 1 264 | 265 | /** \brief Bitdefinition von RXBnSIDL (n = 0, 1) */ 266 | #define SRR 4 267 | #define IDE 3 268 | 269 | /** 270 | * \brief Bitdefinition von RXBnDLC (n = 0, 1) 271 | * \see TXBnDLC (gleiche Bits) 272 | */ 273 | #define RTR 6 274 | #define DLC3 3 275 | #define DLC2 2 276 | #define DLC1 1 277 | #define DLC0 0 278 | 279 | /*@}*/ 280 | #endif // MCP2515_DEFS_H 281 | -------------------------------------------------------------------------------- /logger/pc_interface.cpp: -------------------------------------------------------------------------------- 1 | #include "mcp2515.h" 2 | #include "crc8.h" 3 | #include 4 | 5 | #define ASSERT_CONCAT_(a, b) a##b 6 | #define ASSERT_CONCAT(a, b) ASSERT_CONCAT_(a, b) 7 | #define COMPILE_TIME_ASSERT(e) enum { ASSERT_CONCAT(assert_line_, __LINE__) = 1/(!!(e)) } 8 | 9 | 10 | typedef struct _serial_packet { 11 | // Sentinel value to establish sync 12 | byte sentinel_start; 13 | // Sequence number 14 | uint16_t sequence; 15 | // Little-endian CAN ID (16b) 16 | uint16_t can_id; 17 | // 0 or 1 18 | byte rtr; 19 | // 0 to 8 20 | byte length; 21 | byte data[8]; 22 | byte crc8; 23 | } serial_packet; 24 | 25 | // Constants shared with the Python PC-side interface 26 | const byte PACKET_SIZE = 16; 27 | // How many sync packets comprise a sync frame? 28 | const byte SYNC_PACKETS = 2; 29 | const byte SENTINEL_VALUE = 0xAA; 30 | 31 | // If you get an error here, make sure you've synchronized 32 | // the definitions of the constants with the structure, and 33 | // with the Python PC interface 34 | COMPILE_TIME_ASSERT(PACKET_SIZE == sizeof(serial_packet)); 35 | 36 | // Tunable parameters in this module 37 | // Number of data packets we send between sending sync frames 38 | // Increase this for higher efficiency; decrease it 39 | // - to reduce resync latency 40 | // - if synchronization drift is a problem (frequent resyncs) 41 | const byte sends_per_sync = 126; 42 | // How many packets have we sent since last sync frame? 43 | static byte sends_since_sync = sends_per_sync; 44 | 45 | void upload_CAN_message(tCAN* msg) { 46 | static uint16_t sequence = 0; 47 | serial_packet packet; 48 | if (sends_since_sync == sends_per_sync) { 49 | memset(&packet, 0, PACKET_SIZE); 50 | sends_since_sync = 0; 51 | for (byte i = 0; i < SYNC_PACKETS; i++) 52 | Serial.write((byte*)&packet, PACKET_SIZE); 53 | } 54 | packet.sentinel_start = SENTINEL_VALUE; 55 | packet.sequence = sequence; 56 | packet.can_id = msg->id; 57 | packet.rtr = msg->header.rtr; 58 | packet.length = msg->header.length; 59 | memcpy(packet.data, msg->data, packet.length); 60 | packet.crc8 = crc8((byte*)&packet, PACKET_SIZE - 1); 61 | Serial.write((byte*)&packet, PACKET_SIZE); 62 | sends_since_sync++; 63 | sequence++; 64 | Serial.flush(); 65 | } 66 | -------------------------------------------------------------------------------- /logger/pc_interface.h: -------------------------------------------------------------------------------- 1 | #include "mcp2515.h" 2 | 3 | void upload_CAN_message(tCAN* msg); 4 | -------------------------------------------------------------------------------- /python/arduino.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from struct import unpack 3 | from collections import namedtuple 4 | from time import time 5 | from sys import stderr 6 | 7 | _CANFrame_base = namedtuple('CANFrame', 8 | ['sentinel_start', 'sequence', 'id', 'rtr', 'length', 'data', 'crc', 9 | 'valid_crc']) 10 | 11 | class CANFrame(_CANFrame_base): 12 | def __str__(self): 13 | return "id=%04x, ts=%09d, seq=%07d, data=%s" % (self.id, self.sender_timestamp, self.sequence, 14 | " ".join("%02x" % x for x in self.data[:self.length])) 15 | 16 | 17 | _atm_crc8_table = [ 18 | 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 19 | 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, 20 | 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 21 | 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, 22 | 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 23 | 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, 24 | 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 25 | 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, 26 | 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 27 | 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, 28 | 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 29 | 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, 30 | 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 31 | 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, 32 | 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 33 | 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, 34 | 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 35 | 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, 36 | 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 37 | 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, 38 | 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 39 | 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, 40 | 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 41 | 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, 42 | 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 43 | 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, 44 | 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 45 | 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, 46 | 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 47 | 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, 48 | 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 49 | 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 50 | ] 51 | 52 | class ArduinoSource(object): 53 | PACKET_SIZE = 16 54 | SYNC_PACKETS = 2 55 | START_SENTINEL_VALUE = 0xAA 56 | FORMAT = " 0 22 | 23 | 24 | def check_keyboard(): 25 | if kbhit(): 26 | while kbhit(): 27 | stdin.read(1) 28 | raise KeyboardInterrupt 29 | return 30 | 31 | 32 | def broadcast(sinks, frame): 33 | for sink in sinks: 34 | sink.writeFrame(frame) 35 | check_keyboard() 36 | return 37 | 38 | 39 | def main(): 40 | if len(argv) > 1: 41 | source = HDF5Source(argv[1], ratelimit=True, loop=True) 42 | else: 43 | source = ArduinoSource("COM7", speed=115200) 44 | sinks = [CursesSink(25, 80)] 45 | try: 46 | for i, frame in enumerate(source): 47 | broadcast(sinks, frame) 48 | except KeyboardInterrupt: 49 | pass 50 | 51 | 52 | if __name__ == "__main__": 53 | main() 54 | -------------------------------------------------------------------------------- /python/console.py: -------------------------------------------------------------------------------- 1 | # Windows version of _curses at: 2 | # http://www.lfd.uci.edu/~gohlke/pythonlibs/#curses 3 | import curses 4 | 5 | from collections import defaultdict 6 | from time import time 7 | 8 | from rx8 import RX8State 9 | 10 | def right_pad(line, width): 11 | return line[:width] + (width - len(line)) * " " 12 | 13 | class CursesSink(object): 14 | def __init__(self, height=25, width=80): 15 | self.height = height 16 | self.width = width 17 | self.stdscr = curses.initscr() 18 | curses.noecho() 19 | curses.cbreak() 20 | self.stdscr.keypad(1) 21 | #self.stdscr.resizeterm(height, width) 22 | 23 | self.ids_seen = [] 24 | self.id2lastframe = {} 25 | self.id2arrivals = defaultdict(list) 26 | self.window_length = 256 27 | self.vehicle_state = RX8State() 28 | 29 | def writeFrame(self, frame): 30 | state_changed = self.vehicle_state.update(frame) 31 | self.id2lastframe[frame.id] = frame 32 | arrivals = self.id2arrivals[frame.id] 33 | arrivals.append(time()) 34 | if len(arrivals) > self.window_length: 35 | arrivals.pop(0) 36 | 37 | full_redraw = True 38 | if frame.id not in self.ids_seen: 39 | self.ids_seen.append(frame.id) 40 | self.ids_seen.sort() 41 | full_redraw = True 42 | 43 | vehicle_state_lines = self.vehicle_state.to_string() 44 | n_header_lines = len(vehicle_state_lines) + 1 45 | if full_redraw: 46 | ids_to_draw = self.ids_seen[:self.height - n_header_lines] 47 | elif self.ids_seen.index(frame.id) < self.height - n_header_lines: 48 | ids_to_draw = [frame.id] 49 | else: 50 | ids_to_draw = [] 51 | 52 | if state_changed or full_redraw: 53 | for i, line in enumerate(vehicle_state_lines): 54 | self.stdscr.addstr(i, 0, right_pad(line, self.width)) 55 | self.stdscr.addstr(i + 1, 0, right_pad('', self.width)) 56 | 57 | for id in ids_to_draw: 58 | row = self.ids_seen.index(id) + n_header_lines 59 | frame = self.id2lastframe[id] 60 | arrivals = self.id2arrivals[id] 61 | if len(arrivals) == 1: 62 | rate = "" 63 | else: 64 | if (arrivals[-1] == arrivals[0]): 65 | rate = "---" 66 | else: 67 | fps = len(arrivals) / (arrivals[-1] - arrivals[0]) 68 | if fps < 1: 69 | rate = "% 5d ms/frame" % (1000.0 / fps) 70 | else: 71 | rate = '%.1f' % fps 72 | rate = "% 5s frame/sec" % rate 73 | datatext = " ".join('%02X' % x for x in frame.data) 74 | datatext = datatext + (30 - len(datatext)) * " " 75 | text = "%04X\t%s\t%d\t%s\t%s" % \ 76 | (frame.id, 'T' if frame.rtr else 'F', frame.length, 77 | datatext, rate) 78 | line = right_pad(text, self.width) 79 | self.stdscr.addstr(row, 0, line) 80 | 81 | self.stdscr.refresh() 82 | return 83 | 84 | def __del__(self): 85 | self.stdscr.keypad(0) 86 | curses.nocbreak() 87 | curses.echo() 88 | curses.endwin() 89 | -------------------------------------------------------------------------------- /python/debug-gaps.py: -------------------------------------------------------------------------------- 1 | from hdf5_log import HDF5Source 2 | from sys import argv 3 | import numpy as np 4 | 5 | def main(logfile): 6 | id2frames = {} 7 | frames = [(rts, frame) for rts, frame in 8 | HDF5Source(logfile, timestamps=True)] 9 | frames.sort(key=lambda (rts, frame): (rts, frame.sequence)) 10 | timestamps = np.array([(rts, frame.sender_timestamp) for 11 | rts, frame in frames]) 12 | h5 = tables.openFile(argv[2], 'w') 13 | h5.createArray(h5.root, 'timestamps', timestamps) 14 | h5.close() 15 | return 16 | diffs = np.diff(timestamps, axis=0) 17 | 18 | 19 | for rts, frame in frames: 20 | if frame.id not in id2frames: 21 | id2frames[frame.id] = [] 22 | sts = frame.sender_timestamp 23 | # Normalize timestamps to seconds 24 | id2frames[frame.id].append((sts / 1e3, rts / 1e6, frame)) 25 | print "%08x" % sts, "%.3f" % (rts/1000), frame 26 | print 27 | print 28 | for id, frames in id2frames.iteritems(): 29 | print "ID", hex(id) 30 | last_st, last_rt = frames[0][:2] 31 | for i, (sts, rts, frame) in enumerate(frames[1:]): 32 | if sts - last_st > 1: 33 | print "%08x" % last_st, "%d" % (last_rt/1000), frames[i][2] 34 | print "%08x" % sts, "%d" % (rts/1000), frame 35 | print 36 | last_st = sts 37 | last_rt = rts 38 | print 39 | 40 | 41 | if __name__ == "__main__": 42 | main(argv[1]) 43 | -------------------------------------------------------------------------------- /python/example_log.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihaque/arduino-ecu-logger/e303f19444163650b882aa9a8f86a9ada303dadc/python/example_log.h5 -------------------------------------------------------------------------------- /python/hdf5_log.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | from time import sleep 3 | from time import time 4 | import tables 5 | import numpy as np 6 | 7 | class HDF5Frame(tables.IsDescription): 8 | timestamp = tables.UInt64Col() # Microseconds 9 | sentinel_start = tables.UInt8Col() 10 | sender_timestamp = tables.UInt32Col() 11 | sequence = tables.UInt16Col() 12 | id = tables.UInt16Col() 13 | rtr = tables.BoolCol() 14 | length = tables.UInt8Col() 15 | data = tables.UInt8Col(shape=(8,)) 16 | sentinel_end = tables.UInt8Col() 17 | 18 | CANFrame = namedtuple('CANFrame', [field for field in HDF5Frame.columns 19 | if field != 'timestamp']) 20 | _H5_LOG_TABLE = "CANFrames" 21 | 22 | class HDF5Source(object): 23 | def __init__(self, filename, ratelimit=False, timestamps=False, loop=False): 24 | self.logfile = tables.openFile(filename, 'r') 25 | self.log = self.logfile.root._f_getChild(_H5_LOG_TABLE) 26 | self.timestamps = timestamps 27 | self.rate_limit = ratelimit 28 | self.loop = loop 29 | 30 | def __del__(self): 31 | self.logfile.close() 32 | 33 | def __iter__(self): 34 | while True: 35 | last_returned = float('inf') 36 | last_timestamp = float('inf') 37 | for row in self.log.iterrows(): 38 | if self.rate_limit: 39 | inter_frame_delay = (row['sender_timestamp'] - 40 | last_timestamp) 41 | delay_required = (inter_frame_delay/1e3 - 42 | (time() - last_returned)) 43 | if delay_required > 0: 44 | sleep(delay_required) 45 | 46 | last_returned = time() 47 | last_timestamp = row['sender_timestamp'] 48 | # Truncate data if necessary 49 | kwargs = dict((field, row[field]) 50 | for field in HDF5Frame.columns 51 | if field not in ('data', 'timestamp')) 52 | frame = CANFrame(data=row['data'][:row['length']], **kwargs) 53 | if self.timestamps: 54 | yield row['timestamp'], frame 55 | else: 56 | yield frame 57 | if not self.loop: 58 | break 59 | 60 | 61 | class HDF5Sink(object): 62 | def __init__(self, filename): 63 | self.logfile = tables.openFile(filename, "w") 64 | filters = tables.Filters(complevel=9, complib='zlib') 65 | self.log = self.logfile.createTable(self.logfile.root, _H5_LOG_TABLE, 66 | HDF5Frame, "CAN Frames from Arduino", filters=filters) 67 | self.writes_per_flush = 1024 68 | self.writes_since_flush = 0 69 | self.start_ts = int(time() * 1e6) 70 | return 71 | 72 | def __del__(self): 73 | self.logfile.close() 74 | 75 | def writeFrame(self, frame): 76 | timestamp = int(time() * 1e6) - self.start_ts 77 | h5frame = self.log.row 78 | h5frame['timestamp'] = timestamp 79 | h5frame['sender_timestamp'] = frame.sender_timestamp 80 | h5frame['sentinel_start'] = frame.sentinel_start 81 | h5frame['sequence'] = frame.sequence 82 | h5frame['id'] = frame.id 83 | h5frame['rtr'] = frame.rtr 84 | h5frame['length'] = frame.length 85 | h5frame['sentinel_end'] = frame.sentinel_end 86 | 87 | # Pad data to 8 bytes 88 | data = np.zeros((8,), dtype=np.uint8) 89 | data[:frame.length] = frame.data 90 | h5frame['data'] = data 91 | 92 | h5frame.append() 93 | self.writes_since_flush += 1 94 | if self.writes_since_flush >= self.writes_per_flush: 95 | self.writes_since_flush = 0 96 | self.log.flush() 97 | return 98 | -------------------------------------------------------------------------------- /python/monitor-gaps.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | from sys import argv 3 | from sys import stdin 4 | from sys import stdout 5 | from time import time 6 | 7 | from arduino import ArduinoSource 8 | 9 | import platform 10 | if platform.system() == "Windows": 11 | from msvcrt import kbhit 12 | else: 13 | import select 14 | def kbhit(): 15 | inrdy, _, __ = select.select([sys.stdin], [], [], 0.001) 16 | return len(inrdy) > 0 17 | 18 | 19 | def check_keyboard(): 20 | if kbhit(): 21 | while kbhit(): 22 | stdin.read(1) 23 | raise KeyboardInterrupt 24 | return 25 | 26 | 27 | def broadcast(sinks, frame): 28 | for sink in sinks: 29 | sink.writeFrame(frame) 30 | check_keyboard() 31 | return 32 | 33 | 34 | def main(): 35 | source = ArduinoSource("COM7", speed=115200) 36 | start = time() 37 | last_seq = None 38 | try: 39 | for i, frame in enumerate(source): 40 | if last_seq is not None: 41 | if ((frame.sequence - last_seq) & 0xFFFF) != 1: 42 | print "Out-of-sequence detected at t=%.2f sec: %d, %d" % \ 43 | (time() - start, last_seq, frame.sequence) 44 | else: 45 | print "Started receiving packets" 46 | if i % 10000 == 0: 47 | print "Frame", i 48 | last_seq = frame.sequence 49 | check_keyboard() 50 | except KeyboardInterrupt: 51 | pass 52 | 53 | 54 | if __name__ == "__main__": 55 | main() 56 | -------------------------------------------------------------------------------- /python/plot_logs.py: -------------------------------------------------------------------------------- 1 | from hdf5_log import HDF5Source 2 | from sys import argv 3 | import pylab 4 | from scipy.signal import fftconvolve 5 | from scipy.interpolate import interp1d 6 | import numpy as np 7 | 8 | def to_kph(data): 9 | return ((data[0] * 256.0 + data[1]) - 10000) / 100 10 | 11 | def to_rpm(data): 12 | return (data[0] * 256.0 + data[1]) / 4 13 | 14 | def compute_g(wheelspeed): 15 | lfdata = wheelspeed[0:2] 16 | rfdata = wheelspeed[2:4] 17 | lrdata = wheelspeed[4:6] 18 | rrdata = wheelspeed[6:8] 19 | # 5/18 m/s per kph 20 | vl = 5.0/18 * (to_kph(lfdata) + to_kph(lrdata)) / 2.0 21 | vr = 5.0/18 * (to_kph(rfdata) + to_kph(rrdata)) / 2.0 22 | wheel_sep = 1.65 # meters 23 | return (vl * (vr-vl) / wheel_sep) / 9.8 # 9.8 m/s^2 per g 24 | 25 | def interpolate(timed_data, method='zero'): 26 | t, y = zip(*timed_data) 27 | print "\t",len(t),"frames" 28 | t = np.array(t) 29 | y = np.array(y) 30 | main_interpolator = interp1d(x=t, y=y, kind=method) 31 | def _interp(x): 32 | if x < t[0]: 33 | return y[0] 34 | elif x > t[-1]: 35 | return y[-1] 36 | else: 37 | return main_interpolator(x) 38 | return np.vectorize(_interp) 39 | 40 | 41 | def main(): 42 | # Divide by 1e3 to convert ms -> sec 43 | frames = [(frame.sender_timestamp / 1e3, frame) for frame in 44 | HDF5Source(argv[1])] 45 | frames.sort(key=lambda x:x[0]) 46 | frames = frames[:-1] # Hack, for data files with bad last frame 47 | print "Loaded frames" 48 | start_time = min(ts for ts, frame in frames) 49 | end_time = max(ts for ts, frame in frames) 50 | frames = [(ts - start_time, frame) for ts, frame in frames] 51 | print "Renormalized times" 52 | 53 | speed = interpolate([(ts, to_kph(frame.data[4:6])) 54 | for ts, frame in frames if frame.id == 0x0201]) 55 | print "Constructed speed interpolator" 56 | 57 | rpm = interpolate([(ts, to_rpm(frame.data)) 58 | for ts, frame in frames if frame.id == 0x0201]) 59 | print "Constructed rpm interpolator" 60 | 61 | #throttle = interpolate([(ts, frame.data[6] / 2.0) 62 | # for ts, frame in frames if frame.id == 0x0201]) 63 | #brake = interpolate([(ts, (frame.data[5] & 0x08) >> 3) 64 | # for ts, frame in frames if frame.id == 0x0212]) 65 | lateral_g = interpolate([(ts, compute_g(frame.data)) 66 | for ts, frame in frames if frame.id == 0x04B0]) 67 | print "Constructed g interpolator" 68 | debug_frames = [(ts, frame) for ts, frame in frames if 69 | frame.id == 0xFFFF] 70 | print "Extracted debug frames" 71 | 72 | drive_time_sec = (end_time - start_time) 73 | print start_time 74 | print end_time 75 | print drive_time_sec 76 | print min(ts for ts, frame in frames if frame.id == 0x0201) 77 | print max(ts for ts, frame in frames if frame.id == 0x0201) 78 | print min(ts for ts, frame in frames if frame.id == 0x04B0) 79 | print max(ts for ts, frame in frames if frame.id == 0x04B0) 80 | # Plot at 10Hz from first sec to second to last sec (bounds) 81 | sampling_rate = 10.0 82 | time = np.arange(0, drive_time_sec, 1.0/sampling_rate) 83 | print time.shape 84 | print min(time) 85 | print max(time) 86 | speeds = speed(time) 87 | print "Computed speed" 88 | rpms = rpm(time) 89 | print "Computed rpm" 90 | gs = lateral_g(time) 91 | print "Computed g" 92 | debug_times = [ts for ts, frames in debug_frames] 93 | 94 | fig = pylab.figure() 95 | s1a1 = fig.add_subplot(2,1,1) 96 | pylab.hold(True) 97 | s1a1.stem(debug_times, [150 for x in debug_times], 'k') 98 | s1a1.plot(time, speeds, 'b') 99 | s1a1.set_ylabel('Speed (kph)') 100 | s1a1.set_xlabel('Time (s)') 101 | s1a2 = s1a1.twinx() 102 | s1a2.plot(time, rpms, 'r') 103 | s1a2.set_ylabel('RPM') 104 | s2a1 = fig.add_subplot(2,1,2) 105 | s2a1.plot(time, gs) 106 | s2a1.set_ylabel('Lateral g (+ indicates left turn)') 107 | s2a1.set_xlabel('Time (s)') 108 | pylab.show() 109 | 110 | if __name__ == "__main__": 111 | main() 112 | -------------------------------------------------------------------------------- /python/rx8.py: -------------------------------------------------------------------------------- 1 | def bigendian(data, indices): 2 | num = 0 3 | for index in indices: 4 | num = (num << 8) | data[index] 5 | return num 6 | 7 | 8 | class RX8State(object): 9 | def __init__(self): 10 | self.brake = False 11 | self.handbrake = False 12 | self.dsc = True 13 | self.rpm = 0 14 | self.throttle_pct = 0 15 | self.steering_angle_pct = 0 16 | self.vehicle_speed_kph = 0 17 | self.wheelspeed_lf_kph = 0 18 | self.wheelspeed_rf_kph = 0 19 | self.wheelspeed_lr_kph = 0 20 | self.wheelspeed_rr_kph = 0 21 | 22 | def to_string(self): 23 | def onoff(b): 24 | return 'ON' if b else 'OFF' 25 | 26 | #line1 = ('RPM % 4.1f THROTTLE % 3.1f%% STEERING % 4.1f%% ' % ( 27 | # self.rpm, self.throttle_pct, self.steering_angle_pct)) 28 | # Note: there seems to be a bug either in identification of 29 | # throttle/steering info or in the stored logs. Don't write it 30 | # to screen for now. 31 | line1 = 'RPM % 4.1f ' % ( 32 | self.rpm) 33 | line1 += 'BRAKE % 3s HANDBRAKE % 3s DSC % 3s' % ( 34 | onoff(self.brake), onoff(self.handbrake), 35 | onoff(self.dsc)) 36 | line2 =\ 37 | 'VEHICLE SPEED kph % 3.1f WHEELS kph: LF % 3.1f RF % 3.1f' % ( 38 | self.vehicle_speed_kph, self.wheelspeed_lf_kph, 39 | self.wheelspeed_rf_kph) 40 | line3 =\ 41 | ' LR % 3.1f RR % 3.1f' % ( 42 | self.wheelspeed_lr_kph, self.wheelspeed_rr_kph) 43 | return line1, line2, line3 44 | 45 | @staticmethod 46 | def speed_to_kph(speed): 47 | return (speed - 10000) / 100. 48 | 49 | def update(self, frame): 50 | if frame.id == 0x0081: 51 | # Steering data: bytes 2-3 big endian. Steering angle 52 | # between 0xFDE1 and 0x021E. 53 | angle = bigendian(frame.data, [2,3]) 54 | if angle & 0x8000: 55 | # Negative angle 56 | angle = angle - 0xFFFF - 1 57 | angle /= 543. 58 | self.steering_angle_pct = angle / 543. * 100 59 | return True 60 | elif frame.id == 0x0201: 61 | self.rpm = bigendian(frame.data, [0, 1]) / 4. 62 | self.vehicle_speed_kph =\ 63 | self.speed_to_kph(bigendian(frame.data, [4,5])) 64 | self.throttle_pct = frame.data[6] / 2. 65 | return True 66 | elif frame.id == 0x0212: 67 | self.handbrake = (frame.data[4] & 0x40 != 0) 68 | self.brake = (frame.data[5] & 0x8 != 0) 69 | self.dsc = (frame.data[5] & 0x40 != 0) 70 | return True 71 | elif frame.id == 0x04B0: 72 | self.wheelspeed_lf_kph =\ 73 | self.speed_to_kph(bigendian(frame.data, [0,1])) 74 | self.wheelspeed_rf_kph =\ 75 | self.speed_to_kph(bigendian(frame.data, [2,3])) 76 | self.wheelspeed_lr_kph =\ 77 | self.speed_to_kph(bigendian(frame.data, [4,5])) 78 | self.wheelspeed_rr_kph =\ 79 | self.speed_to_kph(bigendian(frame.data, [6,7])) 80 | return True 81 | else: 82 | return False 83 | -------------------------------------------------------------------------------- /python/text_log.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | from time import time 3 | from arduino import CANFrame 4 | 5 | import numpy as np 6 | 7 | class TextSource(object): 8 | def __init__(self, filename, ratelimit=None): 9 | self.log = open(filename, "rb") 10 | self.interval = 1.0/ratelimit if ratelimit else 0 11 | def __iter__(self): 12 | last_returned = time() - self.interval 13 | for line in self.log: 14 | id, rtr, length, data = line.strip().split("\t") 15 | id = int(id, 16) 16 | rtr = True if rtr == 'T' else False 17 | length = int(length) 18 | data = np.array([int(x, 16) for x in data.split()], dtype=np.uint8) 19 | frame = CANFrame(0xAA, id, rtr, length, data, 0xAA) 20 | 21 | elapsed = time() - last_returned 22 | sleep(max(self.interval - elapsed, 0)) 23 | last_returned = time() 24 | yield frame 25 | 26 | class TextSink(object): 27 | def __init__(self, file): 28 | self.file = file 29 | def writeFrame(self, frame): 30 | text = '%u\t%04X\t%s\t%d\t%s\n' % (frame.sequence, 31 | frame.id, 32 | 'T' if frame.rtr else 'F', 33 | frame.length, 34 | " ".join('%02X' % x for x in frame.data[:frame.length])) 35 | self.file.write(text) 36 | return 37 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihaque/arduino-ecu-logger/e303f19444163650b882aa9a8f86a9ada303dadc/screenshot.png --------------------------------------------------------------------------------