├── pcb └── VpwShield.fzz ├── img └── schematics.jpg ├── .gitignore ├── library.properties ├── .vscode ├── c_cpp_properties.json └── settings.json ├── src ├── common.h ├── interrupts.h ├── storage.h ├── pins.h ├── storage.cpp ├── j1850vpw.h ├── interrupts.cpp ├── pins.cpp └── j1850vpw.cpp ├── keywords.txt ├── LICENSE ├── examples ├── sniffer │ └── sniffer.ino ├── transceiver │ └── transceiver.ino └── j1850-to-serial │ └── j1850-to-serial.ino └── README.md /pcb/VpwShield.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matafonoff/J1850-VPW-Arduino-Transceiver-Library/HEAD/pcb/VpwShield.fzz -------------------------------------------------------------------------------- /img/schematics.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matafonoff/J1850-VPW-Arduino-Transceiver-Library/HEAD/img/schematics.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=J1850 VPW Arduino Transceiver Library 2 | version=1.0.1 3 | author=Steve Matafonov 4 | maintainer=Steve Matafonov 5 | sentence=Arduino library for J1850-VPW-Arduino-Transceiver 6 | paragraph=Arduino library for J1850-VPW-Arduino-Transceiver 7 | category=Communication 8 | url=https://github.com/matafonoff/J1850-VPW-Arduino-Transceiver-Library 9 | architectures=avr 10 | includes=j1850vpw.h 11 | 12 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**" 7 | ], 8 | "defines": [], 9 | "compilerPath": "/usr/bin/gcc", 10 | "cStandard": "gnu18", 11 | "cppStandard": "gnu++14", 12 | "intelliSenseMode": "gcc-x64" 13 | } 14 | ], 15 | "version": 4 16 | } -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | /************************************************************************* 2 | ** AVR J1850VPW VPW Interface 3 | ** by Stepan Matafonov 4 | ** 5 | ** Released under Microsoft Public License 6 | ** 7 | ** contact: matafonoff@gmail.com 8 | ** homepage: xelb.ru 9 | **************************************************************************/ 10 | #pragma once 11 | 12 | #include 13 | 14 | #define BS 12 15 | #define MAX_DATA_LEN (BS - 1) -------------------------------------------------------------------------------- /src/interrupts.h: -------------------------------------------------------------------------------- 1 | /************************************************************************* 2 | ** AVR J1850VPW VPW Interface 3 | ** by Stepan Matafonov 4 | ** 5 | ** Released under Microsoft Public License 6 | ** 7 | ** contact: matafonoff@gmail.com 8 | ** homepage: xelb.ru 9 | **************************************************************************/ 10 | #pragma once 11 | #include 12 | 13 | enum PIN_CHANGE { 14 | PIN_CHANGE_BOTH = 3, 15 | PIN_CHANGE_RISE = 1, 16 | PIN_CHANGE_FALL = 2 17 | }; 18 | 19 | typedef void (*pCallbackFunction)(int state, void* pData); 20 | 21 | void PCattachInterrupt(uint8_t pin, PIN_CHANGE mode, pCallbackFunction pFunc, void* pData); 22 | 23 | void PCdetachInterrupt(uint8_t pin); 24 | 25 | void PCpauseInterrupt(uint8_t pin); 26 | void PCresumeInterrupt(uint8_t pin); 27 | -------------------------------------------------------------------------------- /src/storage.h: -------------------------------------------------------------------------------- 1 | /************************************************************************* 2 | ** AVR J1850VPW VPW Interface 3 | ** by Stepan Matafonov 4 | ** 5 | ** Released under Microsoft Public License 6 | ** 7 | ** contact: matafonoff@gmail.com 8 | ** homepage: xelb.ru 9 | **************************************************************************/ 10 | #pragma once 11 | #include "common.h" 12 | 13 | #ifndef STORAGE_SIZE 14 | #define STORAGE_SIZE 10 15 | #endif 16 | 17 | class StorageItem 18 | { 19 | public: 20 | uint8_t content[BS]; 21 | uint8_t size; 22 | }; 23 | 24 | class Storage 25 | { 26 | volatile int8_t _first; 27 | volatile int8_t _last; 28 | StorageItem _items[STORAGE_SIZE]; 29 | 30 | public: 31 | Storage(); 32 | 33 | void push(uint8_t *buff, uint8_t size); 34 | uint8_t tryPopItem(uint8_t *pBuff); 35 | }; -------------------------------------------------------------------------------- /src/pins.h: -------------------------------------------------------------------------------- 1 | /************************************************************************* 2 | ** AVR J1850VPW VPW Interface 3 | ** by Stepan Matafonov 4 | ** 5 | ** Released under Microsoft Public License 6 | ** 7 | ** contact: matafonoff@gmail.com 8 | ** homepage: xelb.ru 9 | **************************************************************************/ 10 | #pragma once 11 | #include 12 | #include "pins_arduino.h" 13 | #include "interrupts.h" 14 | 15 | 16 | enum PIN_MODES { 17 | PIN_MODE_INPUT = INPUT, 18 | PIN_MODE_INPUT_PULLUP = INPUT_PULLUP, 19 | PIN_MODE_OUTPUT = OUTPUT, 20 | }; 21 | 22 | class Pin { 23 | volatile uint8_t* _reg; 24 | uint8_t _bit; 25 | uint8_t _pin; 26 | 27 | PIN_MODES _mode; 28 | public: 29 | Pin(); 30 | Pin(uint8_t pin, PIN_MODES mode); 31 | ~Pin(); 32 | public: 33 | void write(uint8_t val); 34 | uint8_t read(); 35 | 36 | bool isEmpty() const; 37 | 38 | void attachInterrupt(PIN_CHANGE changeType, pCallbackFunction onPinChaged, void* pData); 39 | void detachInterrupt(); 40 | 41 | void resumeInterrupts(); 42 | void pauseInterrupts(); 43 | }; 44 | 45 | 46 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For J1850-Arduino-Transceiver-Library 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | J1850VPW KEYWORD1 10 | 11 | J1850_ERRORS KEYWORD1 12 | J1850_OK LITERAL1 13 | J1850_ERR_BUS_IS_BUSY LITERAL1 14 | J1850_ERR_BUS_ERROR LITERAL1 15 | J1850_ERR_RECV_NOT_CONFIGURATED LITERAL1 16 | J1850_ERR_PULSE_TOO_SHORT LITERAL1 17 | J1850_ERR_PULSE_OUTSIDE_FRAME LITERAL1 18 | J1850_ERR_ARBITRATION_LOST LITERAL1 19 | J1850_ERR_PULSE_TOO_LONG LITERAL1 20 | 21 | J1850_Operations KEYWORD1 22 | J1850_Write LITERAL1 23 | J1850_Read LITERAL1 24 | 25 | onErrorHandler KEYWORD1 26 | 27 | ####################################### 28 | # Methods and Functions (KEYWORD2) 29 | ####################################### 30 | 31 | setActiveLevel KEYWORD2 32 | init KEYWORD2 33 | tryGetReceivedFrame KEYWORD2 34 | send KEYWORD2 35 | onError KEYWORD2 36 | 37 | 38 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "array": "cpp", 4 | "initializer_list": "cpp", 5 | "string_view": "cpp", 6 | "utility": "cpp", 7 | "atomic": "cpp", 8 | "bit": "cpp", 9 | "*.tcc": "cpp", 10 | "cctype": "cpp", 11 | "clocale": "cpp", 12 | "cmath": "cpp", 13 | "cstdarg": "cpp", 14 | "cstddef": "cpp", 15 | "cstdint": "cpp", 16 | "cstdio": "cpp", 17 | "cstdlib": "cpp", 18 | "cwchar": "cpp", 19 | "cwctype": "cpp", 20 | "deque": "cpp", 21 | "unordered_map": "cpp", 22 | "vector": "cpp", 23 | "exception": "cpp", 24 | "algorithm": "cpp", 25 | "functional": "cpp", 26 | "iterator": "cpp", 27 | "memory": "cpp", 28 | "memory_resource": "cpp", 29 | "numeric": "cpp", 30 | "optional": "cpp", 31 | "random": "cpp", 32 | "string": "cpp", 33 | "system_error": "cpp", 34 | "tuple": "cpp", 35 | "type_traits": "cpp", 36 | "fstream": "cpp", 37 | "iosfwd": "cpp", 38 | "istream": "cpp", 39 | "limits": "cpp", 40 | "new": "cpp", 41 | "ostream": "cpp", 42 | "sstream": "cpp", 43 | "stdexcept": "cpp", 44 | "streambuf": "cpp", 45 | "typeinfo": "cpp" 46 | } 47 | } -------------------------------------------------------------------------------- /src/storage.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************* 2 | ** AVR J1850VPW VPW Interface 3 | ** by Stepan Matafonov 4 | ** 5 | ** Released under Microsoft Public License 6 | ** 7 | ** contact: matafonoff@gmail.com 8 | ** homepage: xelb.ru 9 | **************************************************************************/ 10 | #include "storage.h" 11 | #include 12 | 13 | Storage::Storage() 14 | { 15 | this->_first = -1; 16 | this->_last = -1; 17 | memset(this->_items, 0, sizeof(this->_items)); 18 | } 19 | 20 | void Storage::push(uint8_t *buff, uint8_t size) 21 | { 22 | this->_last++; 23 | if (this->_last >= STORAGE_SIZE) 24 | { 25 | this->_last = 0; 26 | } 27 | 28 | if (this->_first == -1) 29 | { 30 | this->_first = this->_last; 31 | } 32 | else if (this->_first == this->_last) 33 | { 34 | this->_first++; 35 | if (this->_first >= STORAGE_SIZE) 36 | { 37 | this->_first = 0; 38 | } 39 | } 40 | 41 | memcpy(this->_items[_last].content, buff, BS); 42 | this->_items[_last].size = size; 43 | } 44 | 45 | uint8_t Storage::tryPopItem(uint8_t *pBuff) 46 | { 47 | StorageItem *ptr; 48 | 49 | if (this->_first == -1 || this->_last == -1) 50 | { 51 | return 0; 52 | } 53 | 54 | ptr = &this->_items[this->_first]; 55 | 56 | if (this->_first == this->_last) 57 | { 58 | this->_first = -1; 59 | this->_last = -1; 60 | } 61 | else 62 | { 63 | this->_first++; 64 | if (this->_first >= STORAGE_SIZE) 65 | { 66 | this->_first = 0; 67 | } 68 | } 69 | 70 | memcpy(pBuff, ptr->content, ptr->size); 71 | 72 | return ptr->size; 73 | } 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software. 2 | 3 | 1. Definitions 4 | 5 | The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law. A "contribution" is the original software, or any additions or changes to the software. A "contributor" is any person that distributes its contribution under this license. "Licensed patents" are a contributor's patent claims that read directly on its contribution. 6 | 7 | 2. Grant of Rights 8 | 9 | (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. 10 | 11 | (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. 12 | 13 | 3. Conditions and Limitations 14 | 15 | (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. 16 | 17 | (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically. 18 | 19 | (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. 20 | 21 | (D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. 22 | 23 | (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees, or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. -------------------------------------------------------------------------------- /examples/sniffer/sniffer.ino: -------------------------------------------------------------------------------- 1 | 2 | #include "j1850vpw.h" 3 | 4 | #define TX 9 5 | #define RX 10 6 | 7 | void handleError(J1850_Operations op, J1850_ERRORS err); 8 | J1850VPW vpw; 9 | 10 | void setup() 11 | { 12 | Serial.begin(115200); // start serial port 13 | vpw.onError(handleError); // listen for errors 14 | vpw.init(RX); // init transceiver 15 | } 16 | 17 | void loop() 18 | { 19 | static uint8_t buff[BS]; // buffer for read message 20 | static uint8_t sendBuff[2] = {0x01, 0x02}; // message to send, CRC will be read and appended to frame on the fly 21 | 22 | uint8_t dataSize; // size of message 23 | 24 | // read all messages with valid CRC from cache 25 | while (dataSize = vpw.tryGetReceivedFrame(buff)) 26 | { 27 | String s; 28 | 29 | // convert to hex string 30 | uint8_t *pData = buff; 31 | for (int i = 0; i < dataSize; i++) 32 | { 33 | if (i > 0) 34 | { 35 | s += " "; 36 | } 37 | 38 | if (buff[i] < 0x10) 39 | { 40 | s += '0'; 41 | } 42 | 43 | s += String(pData[i], HEX); 44 | } 45 | 46 | Serial.println(s); 47 | } 48 | 49 | // write simple message to bus 50 | vpw.send(sendBuff, sizeof(sendBuff)); 51 | 52 | delay(1000); 53 | } 54 | 55 | 56 | void handleError(J1850_Operations op, J1850_ERRORS err) 57 | { 58 | if (err == J1850_OK) 59 | { 60 | // skip non errors if any 61 | return; 62 | } 63 | 64 | String s = op == J1850_Read ? "READ " : "WRITE "; 65 | switch (err) 66 | { 67 | case J1850_ERR_BUS_IS_BUSY: 68 | Serial.println(s + "J1850_ERR_BUS_IS_BUSY"); 69 | break; 70 | case J1850_ERR_BUS_ERROR: 71 | Serial.println(s + "J1850_ERR_BUS_ERROR"); 72 | break; 73 | case J1850_ERR_RECV_NOT_CONFIGURATED: 74 | Serial.println(s + "J1850_ERR_RECV_NOT_CONFIGURATED"); 75 | break; 76 | case J1850_ERR_PULSE_TOO_SHORT: 77 | Serial.println(s + "J1850_ERR_PULSE_TOO_SHORT"); 78 | break; 79 | case J1850_ERR_PULSE_OUTSIDE_FRAME: 80 | Serial.println(s + "J1850_ERR_PULSE_OUTSIDE_FRAME"); 81 | break; 82 | case J1850_ERR_ARBITRATION_LOST: 83 | Serial.println(s + "J1850_ERR_ARBITRATION_LOST"); 84 | break; 85 | case J1850_ERR_PULSE_TOO_LONG: 86 | Serial.println(s + "J1850_ERR_PULSE_TOO_LONG"); 87 | break; 88 | case J1850_ERR_IFR_RX_NOT_SUPPORTED: 89 | Serial.println(s + "J1850_ERR_IFR_RX_NOT_SUPPORTED"); 90 | break; 91 | default: 92 | // unknown error 93 | Serial.println(s + "ERR: " + String(err, HEX)); 94 | break; 95 | } 96 | } -------------------------------------------------------------------------------- /examples/transceiver/transceiver.ino: -------------------------------------------------------------------------------- 1 | 2 | #include "j1850vpw.h" 3 | 4 | #define TX 9 5 | #define RX 10 6 | 7 | void handleError(J1850_Operations op, J1850_ERRORS err); 8 | 9 | J1850VPW vpw; 10 | 11 | void setup() 12 | { 13 | Serial.begin(115200); // start serial port 14 | vpw.onError(handleError); // listen for errors 15 | vpw.init(RX, TX); // init transceiver 16 | } 17 | 18 | void loop() 19 | { 20 | static uint8_t buff[BS]; // buffer for read message 21 | static uint8_t sendBuff[2] = {0x01, 0x02}; // message to send, CRC will be read and appended to frame on the fly 22 | 23 | uint8_t dataSize; // size of message 24 | 25 | // read all messages with valid CRC from cache 26 | while (dataSize = vpw.tryGetReceivedFrame(buff)) 27 | { 28 | String s; 29 | 30 | // convert to hex string 31 | uint8_t *pData = buff; 32 | for (int i = 0; i < dataSize; i++) 33 | { 34 | if (i > 0) 35 | { 36 | s += " "; 37 | } 38 | 39 | if (buff[i] < 0x10) 40 | { 41 | s += '0'; 42 | } 43 | 44 | s += String(pData[i], HEX); 45 | } 46 | 47 | Serial.println(s); 48 | } 49 | 50 | // write simple message to bus 51 | vpw.send(sendBuff, sizeof(sendBuff)); 52 | 53 | delay(1000); 54 | } 55 | 56 | 57 | void handleError(J1850_Operations op, J1850_ERRORS err) 58 | { 59 | if (err == J1850_OK) 60 | { 61 | // skip non errors if any 62 | return; 63 | } 64 | 65 | String s = op == J1850_Read ? "READ " : "WRITE "; 66 | switch (err) 67 | { 68 | case J1850_ERR_BUS_IS_BUSY: 69 | Serial.println(s + "J1850_ERR_BUS_IS_BUSY"); 70 | break; 71 | case J1850_ERR_BUS_ERROR: 72 | Serial.println(s + "J1850_ERR_BUS_ERROR"); 73 | break; 74 | case J1850_ERR_RECV_NOT_CONFIGURATED: 75 | Serial.println(s + "J1850_ERR_RECV_NOT_CONFIGURATED"); 76 | break; 77 | case J1850_ERR_PULSE_TOO_SHORT: 78 | Serial.println(s + "J1850_ERR_PULSE_TOO_SHORT"); 79 | break; 80 | case J1850_ERR_PULSE_OUTSIDE_FRAME: 81 | Serial.println(s + "J1850_ERR_PULSE_OUTSIDE_FRAME"); 82 | break; 83 | case J1850_ERR_ARBITRATION_LOST: 84 | Serial.println(s + "J1850_ERR_ARBITRATION_LOST"); 85 | break; 86 | case J1850_ERR_PULSE_TOO_LONG: 87 | Serial.println(s + "J1850_ERR_PULSE_TOO_LONG"); 88 | break; 89 | case J1850_ERR_IFR_RX_NOT_SUPPORTED: 90 | Serial.println(s + "J1850_ERR_IFR_RX_NOT_SUPPORTED"); 91 | break; 92 | default: 93 | // unknown error 94 | Serial.println(s + "ERR: " + String(err, HEX)); 95 | break; 96 | } 97 | } -------------------------------------------------------------------------------- /src/j1850vpw.h: -------------------------------------------------------------------------------- 1 | /************************************************************************* 2 | ** AVR J1850VPW VPW Interface 3 | ** by Stepan Matafonov 4 | ** 5 | ** Released under Microsoft Public License 6 | ** 7 | ** contact: matafonoff@gmail.com 8 | ** homepage: xelb.ru 9 | **************************************************************************/ 10 | #pragma once 11 | #include 12 | #include "common.h" 13 | 14 | #include "pins.h" 15 | #include "storage.h" 16 | 17 | enum J1850_ERRORS { 18 | J1850_OK = 0x00, 19 | J1850_ERR_BUS_IS_BUSY = 0x81, 20 | J1850_ERR_BUS_ERROR = 0x82, 21 | J1850_ERR_RECV_NOT_CONFIGURATED = 0x84, 22 | J1850_ERR_PULSE_TOO_SHORT = 0x85, 23 | J1850_ERR_PULSE_OUTSIDE_FRAME = 0x86, 24 | J1850_ERR_ARBITRATION_LOST = 0x87, 25 | J1850_ERR_PULSE_TOO_LONG = 0x88, 26 | J1850_ERR_IFR_RX_NOT_SUPPORTED = 0x89, 27 | J1850_ERR_CRC = 0x90 28 | }; 29 | 30 | enum J1850_Operations { 31 | J1850_Read, 32 | J1850_Write 33 | }; 34 | 35 | typedef void (*onErrorHandler)(J1850_Operations op, J1850_ERRORS err); 36 | 37 | class BitInfo 38 | { 39 | public: 40 | bool isActive; 41 | int length; 42 | }; 43 | 44 | class J1850VPWFriend { 45 | public: 46 | static void __handleRnChange(int state, void* pData) ; 47 | }; 48 | 49 | class J1850VPW { 50 | friend class J1850VPWFriend; 51 | private: 52 | uint8_t ACTIVE; 53 | uint8_t PASSIVE; 54 | int8_t RX; 55 | 56 | Pin __rxPin; 57 | Pin __txPin; 58 | 59 | unsigned long _lastChange; 60 | volatile bool _sofRead; 61 | volatile uint8_t _currState; 62 | volatile uint8_t _bit; 63 | volatile uint8_t _byte; 64 | volatile bool _IFRDetected; 65 | uint8_t _buff[BS]; 66 | uint8_t *msg_buf; 67 | 68 | uint8_t _ignoreList[256/sizeof(uint8_t)]; 69 | 70 | Storage _storage = Storage(); 71 | BitInfo bits[BS]; 72 | 73 | volatile onErrorHandler __errHandler; 74 | 75 | private: 76 | void onFrameRead(); 77 | J1850_ERRORS handleErrorsInternal(J1850_Operations op, J1850_ERRORS err); 78 | void onRxChaged(uint8_t curr); 79 | 80 | uint8_t* getBit(uint8_t id, uint8_t *pBit); 81 | 82 | public: 83 | J1850VPW(); 84 | 85 | J1850VPW* setActiveLevel(uint8_t active); 86 | 87 | J1850VPW* init(uint8_t rxPin, uint8_t txPin); 88 | J1850VPW* init(uint8_t rxPin); 89 | 90 | bool isReadonly() const; 91 | 92 | int8_t tryGetReceivedFrame(uint8_t *pBuff, bool justValid = true); 93 | uint8_t send(uint8_t* pData, uint8_t size, int16_t timeoutMs = -1); 94 | 95 | J1850VPW* listenAll(); 96 | J1850VPW* listen(uint8_t *ids); 97 | 98 | J1850VPW* ignoreAll(); 99 | J1850VPW* ignore(uint8_t *ids); 100 | 101 | J1850VPW* onError(onErrorHandler errHandler); 102 | }; -------------------------------------------------------------------------------- /examples/j1850-to-serial/j1850-to-serial.ino: -------------------------------------------------------------------------------- 1 | 2 | #include "j1850vpw.h" 3 | 4 | #define TX 9 5 | #define RX 10 6 | 7 | void handleError(J1850_Operations op, J1850_ERRORS err); 8 | J1850VPW vpw; 9 | 10 | void setup() 11 | { 12 | Serial.begin(9600); // start serial port 13 | Serial.println("OK"); 14 | delay(300); 15 | if (Serial.available() > 0) { 16 | switch(Serial.read()) { 17 | case '0': Serial.begin(115200); break; 18 | case '1': Serial.begin(56800); break; 19 | case '2': Serial.begin(38400); 20 | default: Serial.begin(19200); break; 21 | } 22 | } 23 | vpw.onError(handleError); // listen for errors 24 | vpw.init(RX, TX); // init transceiver 25 | } 26 | 27 | void loop() 28 | { 29 | static uint8_t buff[BS]; // buffer for read message 30 | static uint8_t sendBuff[2] = {0x01, 0x02}; // message to send, CRC will be read and appended to frame on the fly 31 | 32 | uint8_t dataSize; // size of message 33 | 34 | // read all messages with valid CRC from cache 35 | while (dataSize = vpw.tryGetReceivedFrame(buff)) 36 | { 37 | String s; 38 | 39 | // convert to hex string 40 | uint8_t *pData = buff; 41 | for (int i = 0; i < dataSize; i++) 42 | { 43 | if (i > 0) 44 | { 45 | s += " "; 46 | } 47 | 48 | if (buff[i] < 0x10) 49 | { 50 | s += '0'; 51 | } 52 | 53 | s += String(pData[i], HEX); 54 | } 55 | 56 | Serial.println(s); 57 | } 58 | 59 | // write simple message to bus 60 | vpw.send(sendBuff, sizeof(sendBuff)); 61 | 62 | delay(1000); 63 | } 64 | 65 | 66 | void handleError(J1850_Operations op, J1850_ERRORS err) 67 | { 68 | if (err == J1850_OK) 69 | { 70 | // skip non errors if any 71 | return; 72 | } 73 | 74 | String s = op == J1850_Read ? "READ " : "WRITE "; 75 | switch (err) 76 | { 77 | case J1850_ERR_BUS_IS_BUSY: 78 | Serial.println(s + "J1850_ERR_BUS_IS_BUSY"); 79 | break; 80 | case J1850_ERR_BUS_ERROR: 81 | Serial.println(s + "J1850_ERR_BUS_ERROR"); 82 | break; 83 | case J1850_ERR_RECV_NOT_CONFIGURATED: 84 | Serial.println(s + "J1850_ERR_RECV_NOT_CONFIGURATED"); 85 | break; 86 | case J1850_ERR_PULSE_TOO_SHORT: 87 | Serial.println(s + "J1850_ERR_PULSE_TOO_SHORT"); 88 | break; 89 | case J1850_ERR_PULSE_OUTSIDE_FRAME: 90 | Serial.println(s + "J1850_ERR_PULSE_OUTSIDE_FRAME"); 91 | break; 92 | case J1850_ERR_ARBITRATION_LOST: 93 | Serial.println(s + "J1850_ERR_ARBITRATION_LOST"); 94 | break; 95 | case J1850_ERR_PULSE_TOO_LONG: 96 | Serial.println(s + "J1850_ERR_PULSE_TOO_LONG"); 97 | break; 98 | case J1850_ERR_IFR_RX_NOT_SUPPORTED: 99 | Serial.println(s + "J1850_ERR_IFR_RX_NOT_SUPPORTED"); 100 | break; 101 | default: 102 | // unknown error 103 | Serial.println(s + "ERR: " + String(err, HEX)); 104 | break; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # J1850-VPW-Arduino-Transceiver-Library 2 | Arduino library which allow to communicate on J1850-VPW mode. 3 | It works with wire. Should work with radio and laser transmissions as well. 4 | You just have to choose tx and rx pins. 5 | It doesn't have either a header nor a parity check, just CRC check. 6 | All validations could be implemented at a higher protocol layer. 7 | 8 | This library has been tested with wire connection between two Arduino and J1850 vpw bus of Chrysler Pacifica vehicle. 9 | 10 | ## Connection 11 | Use following schematics to connect Arduino to real J1850VPW bus of your vehicle: 12 | ![schematics](img/schematics.jpg) 13 | 14 | Connections: 15 | * IN -> pin of Arduino that is configured as RX pin for J1850VPW 16 | * OUT -> pin of Arduino that is configured as TX pin for J1850VPW 17 | * BUS -> J1850VPW bus (ex. vehicle) 18 | 19 | ## Code example 20 | ~~~~ 21 | 22 | #include "j1850vpw.h" 23 | 24 | #define TX 9 25 | #define RX 10 26 | 27 | void handleError(J1850_Operations op, J1850_ERRORS err); 28 | 29 | J1850VPW vpw; 30 | 31 | void setup() 32 | { 33 | Serial.begin(115200); // start serial port 34 | vpw.onError(handleError); // listen for errors 35 | vpw.init(RX, TX); // init transceiver 36 | } 37 | 38 | void loop() 39 | { 40 | static uint8_t buff[BS]; // buffer for read message 41 | static uint8_t sendBuff[2] = {0x01, 0x02}; // message to send, CRC will be read and appended to frame on the fly 42 | 43 | uint8_t dataSize; // size of message 44 | 45 | // read all messages with valid CRC from cache 46 | while (dataSize = vpw.tryGetReceivedFrame(buff)) 47 | { 48 | String s; 49 | 50 | // convert to hex string 51 | uint8_t *pData = buff; 52 | for (int i = 0; i < dataSize; i++) 53 | { 54 | if (i > 0) 55 | { 56 | s += " "; 57 | } 58 | 59 | if (buff[i] < 0x10) 60 | { 61 | s += '0'; 62 | } 63 | 64 | s += String(pData[i], HEX); 65 | } 66 | 67 | Serial.println(s); 68 | } 69 | 70 | // write simple message to bus 71 | vpw.send(sendBuff, sizeof(sendBuff)); 72 | 73 | delay(1000); 74 | } 75 | 76 | 77 | void handleError(J1850_Operations op, J1850_ERRORS err) 78 | { 79 | if (err == J1850_OK) 80 | { 81 | // skip non errors if any 82 | return; 83 | } 84 | 85 | String s = op == J1850_Read ? "READ " : "WRITE "; 86 | switch (err) 87 | { 88 | case J1850_ERR_BUS_IS_BUSY: 89 | Serial.println(s + "J1850_ERR_BUS_IS_BUSY"); 90 | break; 91 | case J1850_ERR_BUS_ERROR: 92 | Serial.println(s + "J1850_ERR_BUS_ERROR"); 93 | break; 94 | case J1850_ERR_RECV_NOT_CONFIGURATED: 95 | Serial.println(s + "J1850_ERR_RECV_NOT_CONFIGURATED"); 96 | break; 97 | case J1850_ERR_PULSE_TOO_SHORT: 98 | Serial.println(s + "J1850_ERR_PULSE_TOO_SHORT"); 99 | break; 100 | case J1850_ERR_PULSE_OUTSIDE_FRAME: 101 | Serial.println(s + "J1850_ERR_PULSE_OUTSIDE_FRAME"); 102 | break; 103 | case J1850_ERR_ARBITRATION_LOST: 104 | Serial.println(s + "J1850_ERR_ARBITRATION_LOST"); 105 | break; 106 | case J1850_ERR_PULSE_TOO_LONG: 107 | Serial.println(s + "J1850_ERR_PULSE_TOO_LONG"); 108 | break; 109 | case J1850_ERR_IFR_RX_NOT_SUPPORTED: 110 | Serial.println(s + "J1850_ERR_IFR_RX_NOT_SUPPORTED"); 111 | break; 112 | default: 113 | // unknown error 114 | Serial.println(s + "ERR: " + String(err, HEX)); 115 | break; 116 | } 117 | } 118 | ~~~~ 119 | 120 | #### Based on: 121 | 1. Some code from wiring sources of arduino to implement fast digital IO. 122 | 123 | ## NB! 124 | This software was developed to be used with Chrysler vehicles in a mind. This means that the current protocol implementation misses some features like (IFR). 125 | 126 | If you have any ideas of how to implement features you need - feel free to make a pull request. In this case you will be mentioned as a library developer and this library will be developed much faster. 127 | 128 | ## P.S. 129 | This library is rewritten by me to SM chips and I'm working on that code primerially. And yes, I'm not working with Arduino for few monthes already. 130 | This means I have little time to support Arduino implementation. So if you have any code updates - feel free to use pull requests. 131 | -------------------------------------------------------------------------------- /src/interrupts.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************* 2 | ** AVR J1850VPW VPW Interface 3 | ** by Stepan Matafonov 4 | ** 5 | ** Released under Microsoft Public License 6 | ** 7 | ** contact: matafonoff@gmail.com 8 | ** homepage: xelb.ru 9 | **************************************************************************/ 10 | #include "interrupts.h" 11 | 12 | volatile uint8_t *port_to_pcmask[] = { 13 | &PCMSK0, 14 | &PCMSK1, 15 | &PCMSK2}; 16 | 17 | static PIN_CHANGE PCintMode[24]; 18 | 19 | volatile static pCallbackFunction PCintFunc[24] = {NULL}; 20 | volatile static void* PCintData[24] = {NULL}; 21 | volatile static bool PCintActive[24] = {NULL}; 22 | 23 | volatile static uint8_t PCintLast[3]; 24 | 25 | bool initPinInfo(uint8_t pin, uint8_t *pPort, uint8_t *pSlot) 26 | { 27 | uint8_t port = digitalPinToPort(pin); 28 | // map pin to PCIR register 29 | if (port == NOT_A_PORT) 30 | { 31 | return false; 32 | } 33 | 34 | port -= 2; 35 | 36 | // -- Fix by Baziki. In the original sources it was a little bug, which cause analog ports to work incorrectly. 37 | if (port == 1) 38 | { 39 | *pSlot = port * 8 + (pin - 14); 40 | } 41 | else 42 | { 43 | *pSlot = port * 8 + (pin % 8); 44 | } 45 | // --Fix end 46 | 47 | *pPort = port; 48 | 49 | return true; 50 | } 51 | 52 | void PCattachInterrupt(uint8_t pin, PIN_CHANGE mode, pCallbackFunction pFunc, void* pData) 53 | { 54 | uint8_t port; 55 | uint8_t slot; 56 | // map pin to PCIR register 57 | if (!initPinInfo(pin, &port, &slot)) 58 | { 59 | return; 60 | } 61 | 62 | uint8_t bit = digitalPinToBitMask(pin); 63 | volatile uint8_t *pcmask = port_to_pcmask[port]; 64 | 65 | PCintMode[slot] = mode; 66 | PCintFunc[slot] = pFunc; 67 | PCintData[slot] = pData; 68 | PCintActive[slot] = true; 69 | 70 | // set the mask 71 | *pcmask |= bit; 72 | // enable the interrupt 73 | PCICR |= 0x01 << port; 74 | } 75 | 76 | void PCdetachInterrupt(uint8_t pin) 77 | { 78 | uint8_t bit = digitalPinToBitMask(pin); 79 | uint8_t port = digitalPinToPort(pin); 80 | volatile uint8_t *pcmask; 81 | 82 | // map pin to PCIR register 83 | if (port == NOT_A_PORT) 84 | { 85 | return; 86 | } 87 | else 88 | { 89 | port -= 2; 90 | pcmask = port_to_pcmask[port]; 91 | } 92 | 93 | // disable the mask. 94 | *pcmask &= ~bit; 95 | // if that's the last one, disable the interrupt. 96 | if (*pcmask == 0) 97 | { 98 | PCICR &= ~(0x01 << port); 99 | } 100 | } 101 | 102 | void PCpauseInterrupt(uint8_t pin) 103 | { 104 | uint8_t port; 105 | uint8_t slot; 106 | // map pin to PCIR register 107 | if (!initPinInfo(pin, &port, &slot)) 108 | { 109 | return; 110 | } 111 | 112 | PCintActive[slot] = false; 113 | } 114 | void PCresumeInterrupt(uint8_t pin) 115 | { 116 | uint8_t port; 117 | uint8_t slot; 118 | // map pin to PCIR register 119 | if (!initPinInfo(pin, &port, &slot)) 120 | { 121 | return; 122 | } 123 | 124 | PCintActive[slot] = true; 125 | } 126 | 127 | // common code for isr handler. "port" is the PCINT number. 128 | // there isn't really a good way to back-map ports and masks to pins. 129 | static void PCint(uint8_t port) 130 | { 131 | uint8_t bit; 132 | uint8_t curr; 133 | uint8_t mask; 134 | uint8_t pin; 135 | 136 | // get the pin states for the indicated port. 137 | curr = *portInputRegister(port + 2); 138 | mask = curr ^ PCintLast[port]; 139 | PCintLast[port] = curr; 140 | 141 | // mask is pins that have changed. screen out non pcint pins. 142 | if ((mask &= *port_to_pcmask[port]) == 0) 143 | { 144 | return; 145 | } 146 | 147 | // mask is pcint pins that have changed. 148 | for (uint8_t i = 0; i < 8; i++) 149 | { 150 | bit = 0x01 << i; 151 | if ((bit & mask)) 152 | { 153 | pin = port * 8 + i; 154 | 155 | // Trigger interrupt if mode is CHANGE, or if mode is RISING and 156 | // the bit is currently high, or if mode is FALLING and bit is low. 157 | uint8_t currValue = curr & bit ? HIGH : LOW; 158 | if ((PCintFunc[pin] != NULL && PCintActive[pin]) && 159 | (PCintMode[pin] == PIN_CHANGE_BOTH || ((PCintMode[pin] == PIN_CHANGE_RISE) && currValue) || ((PCintMode[pin] == PIN_CHANGE_FALL) && !currValue))) 160 | { 161 | PCintFunc[pin](currValue, PCintData[pin]); 162 | } 163 | } 164 | } 165 | } 166 | 167 | SIGNAL(PCINT0_vect) 168 | { 169 | PCint(0); 170 | } 171 | SIGNAL(PCINT1_vect) 172 | { 173 | PCint(1); 174 | } 175 | SIGNAL(PCINT2_vect) 176 | { 177 | PCint(2); 178 | } -------------------------------------------------------------------------------- /src/pins.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************* 2 | ** AVR J1850VPW VPW Interface 3 | ** by Stepan Matafonov 4 | ** 5 | ** Released under Microsoft Public License 6 | ** 7 | ** contact: matafonoff@gmail.com 8 | ** homepage: xelb.ru 9 | **************************************************************************/ 10 | #include "pins.h" 11 | #include "wiring_private.h" 12 | 13 | static void turnOffPWM(uint8_t timer) 14 | { 15 | switch (timer) 16 | { 17 | #if defined(TCCR1A) && defined(COM1A1) 18 | case TIMER1A: 19 | cbi(TCCR1A, COM1A1); 20 | break; 21 | #endif 22 | #if defined(TCCR1A) && defined(COM1B1) 23 | case TIMER1B: 24 | cbi(TCCR1A, COM1B1); 25 | break; 26 | #endif 27 | #if defined(TCCR1A) && defined(COM1C1) 28 | case TIMER1C: 29 | cbi(TCCR1A, COM1C1); 30 | break; 31 | #endif 32 | 33 | #if defined(TCCR2) && defined(COM21) 34 | case TIMER2: 35 | cbi(TCCR2, COM21); 36 | break; 37 | #endif 38 | 39 | #if defined(TCCR0A) && defined(COM0A1) 40 | case TIMER0A: 41 | cbi(TCCR0A, COM0A1); 42 | break; 43 | #endif 44 | 45 | #if defined(TCCR0A) && defined(COM0B1) 46 | case TIMER0B: 47 | cbi(TCCR0A, COM0B1); 48 | break; 49 | #endif 50 | #if defined(TCCR2A) && defined(COM2A1) 51 | case TIMER2A: 52 | cbi(TCCR2A, COM2A1); 53 | break; 54 | #endif 55 | #if defined(TCCR2A) && defined(COM2B1) 56 | case TIMER2B: 57 | cbi(TCCR2A, COM2B1); 58 | break; 59 | #endif 60 | 61 | #if defined(TCCR3A) && defined(COM3A1) 62 | case TIMER3A: 63 | cbi(TCCR3A, COM3A1); 64 | break; 65 | #endif 66 | #if defined(TCCR3A) && defined(COM3B1) 67 | case TIMER3B: 68 | cbi(TCCR3A, COM3B1); 69 | break; 70 | #endif 71 | #if defined(TCCR3A) && defined(COM3C1) 72 | case TIMER3C: 73 | cbi(TCCR3A, COM3C1); 74 | break; 75 | #endif 76 | 77 | #if defined(TCCR4A) && defined(COM4A1) 78 | case TIMER4A: 79 | cbi(TCCR4A, COM4A1); 80 | break; 81 | #endif 82 | #if defined(TCCR4A) && defined(COM4B1) 83 | case TIMER4B: 84 | cbi(TCCR4A, COM4B1); 85 | break; 86 | #endif 87 | #if defined(TCCR4A) && defined(COM4C1) 88 | case TIMER4C: 89 | cbi(TCCR4A, COM4C1); 90 | break; 91 | #endif 92 | #if defined(TCCR4C) && defined(COM4D1) 93 | case TIMER4D: 94 | cbi(TCCR4C, COM4D1); 95 | break; 96 | #endif 97 | 98 | #if defined(TCCR5A) 99 | case TIMER5A: 100 | cbi(TCCR5A, COM5A1); 101 | break; 102 | case TIMER5B: 103 | cbi(TCCR5A, COM5B1); 104 | break; 105 | case TIMER5C: 106 | cbi(TCCR5A, COM5C1); 107 | break; 108 | #endif 109 | } 110 | } 111 | 112 | bool Pin::isEmpty() const 113 | { 114 | return this->_reg == NULL; 115 | } 116 | 117 | void Pin::write(uint8_t val) 118 | { 119 | uint8_t oldSREG = SREG; 120 | cli(); 121 | 122 | if (val == LOW) 123 | { 124 | *this->_reg &= ~this->_bit; 125 | } 126 | else 127 | { 128 | *this->_reg |= this->_bit; 129 | } 130 | 131 | SREG = oldSREG; 132 | } 133 | 134 | uint8_t Pin::read() 135 | { 136 | if (*this->_reg & this->_bit) 137 | return HIGH; 138 | return LOW; 139 | } 140 | 141 | void Pin::attachInterrupt(PIN_CHANGE changeType, pCallbackFunction onPinChaged, void* pData) 142 | { 143 | PCattachInterrupt(this->_pin, changeType, onPinChaged, pData); 144 | } 145 | void Pin::detachInterrupt() 146 | { 147 | PCdetachInterrupt(this->_pin); 148 | } 149 | 150 | void Pin::resumeInterrupts() 151 | { 152 | PCresumeInterrupt(this->_pin); 153 | } 154 | 155 | void Pin::pauseInterrupts() 156 | { 157 | PCpauseInterrupt(this->_pin); 158 | } 159 | 160 | Pin ::~Pin() 161 | { 162 | this->detachInterrupt(); 163 | } 164 | 165 | Pin::Pin() 166 | { 167 | this->_reg = NULL; 168 | this->_bit = -1; 169 | this->_mode = 0; 170 | this->_pin = -1; 171 | } 172 | 173 | Pin::Pin(uint8_t pin, PIN_MODES mode) 174 | { 175 | uint8_t timer = digitalPinToTimer(pin); 176 | uint8_t bit = digitalPinToBitMask(pin); 177 | uint8_t port = digitalPinToPort(pin); 178 | 179 | if (port == NOT_A_PIN) 180 | { 181 | return; 182 | } 183 | 184 | // If the pin that support PWM output, we need to turn it off 185 | // before getting a digital reading. 186 | if (timer != NOT_ON_TIMER) 187 | { 188 | turnOffPWM(timer); 189 | } 190 | 191 | // JWS: can I let the optimizer do this? 192 | volatile uint8_t *reg; 193 | volatile uint8_t *out; 194 | reg = portModeRegister(port); 195 | out = portOutputRegister(port); 196 | if (mode == PIN_MODE_INPUT) 197 | { 198 | uint8_t oldSREG = SREG; 199 | cli(); 200 | *reg &= ~bit; 201 | *out &= ~bit; 202 | SREG = oldSREG; 203 | } 204 | else if (mode == PIN_MODE_INPUT_PULLUP) 205 | { 206 | uint8_t oldSREG = SREG; 207 | cli(); 208 | *reg &= ~bit; 209 | *out |= bit; 210 | SREG = oldSREG; 211 | } 212 | else 213 | { 214 | uint8_t oldSREG = SREG; 215 | cli(); 216 | *reg |= bit; 217 | SREG = oldSREG; 218 | } 219 | 220 | if (mode == PIN_MODE_OUTPUT) 221 | { 222 | reg = portOutputRegister(port); 223 | } 224 | else 225 | { 226 | reg = portInputRegister(port); 227 | } 228 | 229 | this->_reg = reg; 230 | this->_bit = bit; 231 | this->_mode = mode; 232 | this->_pin = pin; 233 | } 234 | -------------------------------------------------------------------------------- /src/j1850vpw.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************* 2 | ** AVR J1850VPW VPW Interface 3 | ** by Stepan Matafonov 4 | ** 5 | ** Released under Microsoft Public License 6 | ** 7 | ** contact: matafonoff@gmail.com 8 | ** homepage: xelb.ru 9 | **************************************************************************/ 10 | #include "j1850vpw.h" 11 | #include 12 | #include 13 | #include "interrupts.h" 14 | 15 | #define MAX(a, b) ((a) < (b) ? (b) : (a)) 16 | 17 | #define MAX_uS ((unsigned long )-1L) 18 | 19 | #define RX_SOF_MIN (163) 20 | #define RX_SOF_MAX (239) 21 | 22 | #define RX_EOD_MIN (164) // minimum end of data time 23 | #define RX_EOF_MIN (239) // minimum end of data time 24 | 25 | #define RX_SHORT_MIN (34) // minimum short pulse time 26 | #define RX_SHORT_MAX (96) // maximum short pulse time 27 | #define RX_SHORT_IGNORE (5) // Ignore pulses shorter than this to reduce noise 28 | 29 | #define RX_LONG_MIN (97) // minimum long pulse time 30 | #define RX_LONG_MAX (163) // maximum long pulse time 31 | 32 | #define RX_ARBITRATION_TOL (10) // Tolerance for shorter passive pulses from other modules during arbitration 33 | #define PROPOGATION_DELAY (10) //Propogation delay from driving output Passive to checking input follows 34 | 35 | 36 | #define RX_EOD_MAX (239) // maximum end of data time 37 | 38 | #define RX_PULSE_MAX (3000) 39 | 40 | #define TX_SHORT (64) // Short pulse nominal time 41 | #define TX_LONG (128) // Long pulse nominal time 42 | #define TX_SOF (200) // Start Of Frame nominal time 43 | #define TX_EOD (200) // End Of Data nominal time 44 | #define TX_EOF (280) // End Of Frame nominal time 45 | 46 | #define IS_BETWEEN(x, min, max) (x >= min && x <= max) 47 | 48 | uint8_t crc(uint8_t *msg_buf, int8_t nbytes) 49 | { 50 | uint8_t crc_reg = 0xff, poly, byte_count, bit_count; 51 | uint8_t *byte_point; 52 | uint8_t bit_point; 53 | 54 | for (byte_count = 0, byte_point = msg_buf; byte_count < nbytes; ++byte_count, ++byte_point) 55 | { 56 | for (bit_count = 0, bit_point = 0x80; bit_count < 8; ++bit_count, bit_point >>= 1) 57 | { 58 | if (bit_point & *byte_point) // case for new bit = 1 59 | { 60 | if (crc_reg & 0x80) 61 | poly = 1; // define the polynomial 62 | else 63 | poly = 0x1c; 64 | crc_reg = ((crc_reg << 1) | 1) ^ poly; 65 | } 66 | else // case for new bit = 0 67 | { 68 | poly = 0; 69 | if (crc_reg & 0x80) 70 | poly = 0x1d; 71 | crc_reg = (crc_reg << 1) ^ poly; 72 | } 73 | } 74 | } 75 | return ~crc_reg; // Return CRC 76 | } 77 | 78 | 79 | J1850_ERRORS J1850VPW::handleErrorsInternal(J1850_Operations op, J1850_ERRORS err) 80 | { 81 | if (err != J1850_OK) 82 | { 83 | onErrorHandler errHandler = __errHandler; 84 | if (errHandler) 85 | { 86 | errHandler(op, err); 87 | } 88 | } 89 | 90 | return err; 91 | } 92 | 93 | bool J1850VPW::isReadonly() const { 94 | return __txPin.isEmpty(); 95 | } 96 | 97 | void J1850VPW::onRxChaged(uint8_t curr) 98 | { 99 | _currState = curr; 100 | curr = !curr; 101 | 102 | unsigned long now = micros(); 103 | unsigned long diff; 104 | 105 | if (now < _lastChange) 106 | { 107 | // overflow occured 108 | diff = now + (MAX_uS - _lastChange); 109 | } 110 | else 111 | { 112 | diff = now - _lastChange; 113 | } 114 | 115 | if (diff < RX_SHORT_IGNORE) //We filter out some noise for very short pulses around the transition 116 | { 117 | return; 118 | } 119 | 120 | _lastChange = now; 121 | 122 | if (diff < RX_SHORT_MIN) 123 | { 124 | // too short to be a valid pulse. Data error 125 | _sofRead = false; 126 | handleErrorsInternal(J1850_Read, J1850_ERR_PULSE_TOO_SHORT); 127 | return; 128 | } 129 | 130 | if (!_sofRead) 131 | { 132 | if (_sofRead = (curr == ACTIVE && IS_BETWEEN(diff, RX_SOF_MIN, RX_SOF_MAX))) 133 | { 134 | _byte = 0; 135 | _bit = 0; 136 | msg_buf = _buff; 137 | *msg_buf = 0; 138 | _IFRDetected = false; 139 | } 140 | else 141 | { 142 | handleErrorsInternal(J1850_Read, J1850_ERR_PULSE_OUTSIDE_FRAME); 143 | } 144 | return; 145 | } 146 | 147 | *msg_buf <<= 1; 148 | if (curr == PASSIVE) 149 | { 150 | if (diff > RX_EOF_MIN) 151 | { 152 | // data ended - copy package to buffer 153 | _sofRead = false; 154 | 155 | if (!_IFRDetected) 156 | { 157 | onFrameRead(); 158 | } 159 | return; 160 | } 161 | if (!_IFRDetected && IS_BETWEEN(diff, RX_EOD_MIN, RX_EOD_MAX)) 162 | { 163 | // data ended and IFR detected - set flag to ignore the incoming IFR and flag error 164 | _IFRDetected = true; 165 | handleErrorsInternal(J1850_Read, J1850_ERR_IFR_RX_NOT_SUPPORTED); 166 | onFrameRead(); 167 | return; 168 | } 169 | if (!_IFRDetected && IS_BETWEEN(diff, RX_LONG_MIN, RX_LONG_MAX)) 170 | { 171 | *msg_buf |= 1; 172 | } 173 | } 174 | else if (!_IFRDetected) 175 | { 176 | if (diff <= RX_SHORT_MAX) 177 | { 178 | *msg_buf |= 1; 179 | } 180 | } 181 | if(!_IFRDetected) 182 | { 183 | _bit++; 184 | if (_bit == 8) 185 | { 186 | _byte++; 187 | msg_buf++; 188 | *msg_buf = 0; 189 | _bit = 0; 190 | } 191 | } 192 | if (_byte == BS) 193 | { 194 | _sofRead = false; 195 | if (!_IFRDetected) 196 | { 197 | onFrameRead(); 198 | } 199 | } 200 | } 201 | 202 | J1850VPW::J1850VPW() 203 | : ACTIVE(LOW) 204 | , PASSIVE(HIGH) 205 | , RX(-1) 206 | , __rxPin(Pin()) 207 | , __txPin(Pin()) 208 | , _lastChange(micros()) 209 | , _sofRead(false) 210 | , _currState(ACTIVE) 211 | , _bit(0) 212 | , _byte(0) 213 | , msg_buf(NULL) 214 | , _storage(Storage()) 215 | , __errHandler(NULL) 216 | { 217 | listenAll(); 218 | } 219 | 220 | J1850VPW* J1850VPW::setActiveLevel(uint8_t active) 221 | { 222 | if (active == LOW) 223 | { 224 | ACTIVE = LOW; 225 | PASSIVE = HIGH; 226 | } 227 | else 228 | { 229 | ACTIVE = HIGH; 230 | PASSIVE = LOW; 231 | } 232 | 233 | return this; 234 | } 235 | 236 | J1850VPW* J1850VPW::init(uint8_t rxPin, uint8_t txPin) 237 | { 238 | init(rxPin); 239 | 240 | __txPin = Pin(txPin, PIN_MODE_OUTPUT); 241 | __txPin.write(PASSIVE); 242 | 243 | return this; 244 | } 245 | 246 | void J1850VPWFriend::__handleRnChange(int state, void* pData) { 247 | ((J1850VPW*)pData)->onRxChaged(state); 248 | } 249 | 250 | J1850VPW* J1850VPW::init(uint8_t rxPin) 251 | { 252 | __rxPin = Pin(rxPin, PIN_MODE_INPUT_PULLUP); 253 | 254 | _currState = __rxPin.read(); 255 | 256 | __rxPin.attachInterrupt(PIN_CHANGE_BOTH, &J1850VPWFriend::__handleRnChange, this); 257 | 258 | return this; 259 | } 260 | 261 | // NB! Performance critical!!! Do not split 262 | uint8_t J1850VPW::send(uint8_t *pData, uint8_t nbytes, int16_t timeoutMs /*= -1*/) 263 | { 264 | if (isReadonly()) 265 | { 266 | return handleErrorsInternal(J1850_Write, J1850_ERR_RECV_NOT_CONFIGURATED); 267 | } 268 | 269 | uint8_t result = J1850_OK; 270 | static uint8_t buff[BS]; 271 | memcpy(buff, pData, nbytes); 272 | buff[nbytes] = crc(buff, nbytes); 273 | nbytes++; 274 | 275 | // Serial.print("QQQ: "); 276 | // for (int q = 0; q < nbytes; q++) { 277 | // uint8_t w = buff[q]; 278 | // if (w < 0x10) { 279 | // Serial.print('0'); 280 | // } 281 | // Serial.print(String(w, HEX)); 282 | // } 283 | // Serial.println(); 284 | 285 | 286 | uint8_t *msg_buf = buff; 287 | unsigned long now; 288 | 289 | // wait for idle 290 | if (timeoutMs >= 0) 291 | { 292 | now = micros(); 293 | unsigned long start = now; 294 | timeoutMs *= 1000; // convert to microseconds 295 | while (micros() - now < TX_EOF) 296 | { 297 | if (__rxPin.read() == ACTIVE) 298 | { 299 | now = micros(); 300 | } 301 | 302 | if (micros() - start > timeoutMs) 303 | { 304 | result = J1850_ERR_BUS_IS_BUSY; 305 | goto stop; 306 | } 307 | } 308 | } 309 | 310 | // SOF 311 | __rxPin.pauseInterrupts(); 312 | __txPin.write(ACTIVE); 313 | now = micros(); 314 | while (micros() - now < TX_SOF) 315 | ; 316 | 317 | // send data 318 | do 319 | { 320 | uint8_t temp_byte = *msg_buf; // store byte temporary 321 | uint8_t nbits = 8; 322 | unsigned long delay; 323 | while (nbits--) // send 8 bits 324 | { 325 | if (nbits & 1) // start allways with passive symbol 326 | { 327 | delay = (temp_byte & 0x80) ? TX_LONG : TX_SHORT; // send correct pulse lenght 328 | __txPin.write(PASSIVE); // set bus active 329 | now = micros(); 330 | delayMicroseconds(PROPOGATION_DELAY); // Allow time for RX to follow TX for fast processors 331 | while (micros() - now < delay) 332 | { 333 | if (__rxPin.read() == ACTIVE) 334 | { 335 | if (delay - (micros() - now) < RX_ARBITRATION_TOL) //Arbitration not lost 336 | { 337 | now = micros() - delay - 1; //resync to faster module 338 | } else //We lost arbitration so drop out 339 | { 340 | result = J1850_ERR_ARBITRATION_LOST; 341 | goto stop; 342 | } 343 | } 344 | } 345 | } 346 | else // send active symbol 347 | { 348 | delay = (temp_byte & 0x80) ? TX_SHORT : TX_LONG; // send correct pulse lenght 349 | __txPin.write(ACTIVE); // set bus active 350 | now = micros(); 351 | while (micros() - now < delay) 352 | ; 353 | } 354 | 355 | temp_byte <<= 1; // next bit 356 | } // end nbits while loop 357 | ++msg_buf; // next byte from buffer 358 | } while (--nbytes); // end nbytes do loop 359 | 360 | // EOF 361 | __txPin.write(PASSIVE); 362 | now = micros(); 363 | delayMicroseconds(PROPOGATION_DELAY); // Allow time for RX to follow TX for fast processors 364 | while (micros() - now < TX_SOF) 365 | { 366 | if (__rxPin.read() == ACTIVE) 367 | { 368 | result = J1850_ERR_ARBITRATION_LOST; 369 | goto stop; 370 | } 371 | } 372 | 373 | stop: 374 | __rxPin.resumeInterrupts(); 375 | return handleErrorsInternal(J1850_Write, result); 376 | } 377 | 378 | J1850VPW* J1850VPW::onError(onErrorHandler errHandler) 379 | { 380 | __errHandler = errHandler; 381 | return this; 382 | } 383 | 384 | int8_t J1850VPW::tryGetReceivedFrame(uint8_t *pBuff, bool justValid /*= true*/) 385 | { 386 | uint8_t size; 387 | bool crcOK = true; 388 | 389 | while (true) 390 | { 391 | size = _storage.tryPopItem(pBuff); 392 | if (!size) 393 | { 394 | return 0; 395 | } 396 | if (crc(pBuff, size - 1) != pBuff[size - 1]) 397 | { 398 | crcOK = false; 399 | handleErrorsInternal(J1850_Read, J1850_ERR_CRC); 400 | } 401 | if (!justValid || crcOK) 402 | { 403 | break; 404 | } 405 | } 406 | 407 | return size; 408 | } 409 | 410 | 411 | void J1850VPW::onFrameRead() 412 | { 413 | if (!IS_BETWEEN(_byte, 2, BS)) 414 | { 415 | return; 416 | } 417 | 418 | uint8_t *pByte, bit; 419 | 420 | pByte = getBit(_buff[0], &bit); 421 | if (pByte && (*pByte & (1 << bit)) == 0) { 422 | _storage.push(_buff, _byte); 423 | } 424 | } 425 | 426 | 427 | J1850VPW* J1850VPW::listenAll() { 428 | memset(_ignoreList, 0, sizeof(_ignoreList)); 429 | return this; 430 | } 431 | 432 | J1850VPW* J1850VPW::listen(uint8_t *ids) { 433 | uint8_t *pByte, bit; 434 | while (*ids) { 435 | pByte = getBit(*ids, &bit); 436 | 437 | if (pByte) { 438 | *pByte &= ~(1 << bit); 439 | } 440 | 441 | ids++; 442 | } 443 | return this; 444 | } 445 | 446 | J1850VPW* J1850VPW::ignoreAll() { 447 | memset(_ignoreList, 0xff, sizeof(_ignoreList)); 448 | return this; 449 | } 450 | 451 | J1850VPW* J1850VPW::ignore(uint8_t *ids) { 452 | uint8_t *pByte, bit; 453 | while (*ids) { 454 | pByte = getBit(*ids, &bit); 455 | 456 | if (pByte) { 457 | *pByte |= 1 << bit; 458 | } 459 | 460 | ids++; 461 | } 462 | return this; 463 | } 464 | 465 | uint8_t* J1850VPW::getBit(uint8_t id, uint8_t *pBit) { 466 | if (!id) { 467 | *pBit = 0xff; 468 | return NULL; 469 | } 470 | 471 | *pBit = id % 8; 472 | return &(_ignoreList[id / 8]); 473 | } --------------------------------------------------------------------------------