From a899ff745b525c68bbf9ea24f3e437dc2d1ebd94 Mon Sep 17 00:00:00 2001 From: Baozhu Zuo Date: Thu, 10 Dec 2020 11:28:49 +0800 Subject: [PATCH] Add CAN BUS Serial monitor example --- can-serial.cpp | 556 ++++++++++++++++++ can-serial.h | 244 ++++++++ examples/canbus-monitor/README.md | 62 ++ .../canbus-monitor/arduino-canbus-monitor.ino | 68 +++ 4 files changed, 930 insertions(+) create mode 100644 can-serial.cpp create mode 100644 can-serial.h create mode 100644 examples/canbus-monitor/README.md create mode 100644 examples/canbus-monitor/arduino-canbus-monitor.ino diff --git a/can-serial.cpp b/can-serial.cpp new file mode 100644 index 0000000..b151e3c --- /dev/null +++ b/can-serial.cpp @@ -0,0 +1,556 @@ +/***************************************************************************************** +* This is implementation of CAN BUS ASCII protocol based on LAWICEL v1.3 serial protocol +* of CanSerial/CANUSB device (http://www.CanSerial.com/docs/CanSerial_v3.pdf) +* +* Made for Arduino with Seeduino/ElecFreaks CAN BUS Shield based on MCP2515 +* +* Copyright (C) 2015 Anton Viktorov +* https://github.com/latonita/can-ascii +* +* This library is free software. You may use/redistribute it under The MIT License terms. +* +*****************************************************************************************/ + +#include +#include "mcp_can.h" +#include "can-serial.h" + +#define LOGGING_ENABLED + +#ifdef LOGGING_ENABLED +#define dbg_begin(x) debug.begin(x) +#define dbg0(x) debug.print(x) +#define dbg1(x) debug.println(x) +#define dbg2(x,y) debug.print(x); debug.println(y) +#define dbgH(x) debug.print(x,HEX) +#define DEBUG_RX_PIN 8 +#define DEBUG_TX_PIN 9 +#else +#define dbg_begin(x) +#define dbg0(x) +#define dbg1(x) +#define dbg2(x,y) +#define dbgH(x) +#endif + +#ifdef LOGGING_ENABLED + // software serial #2: TX = digital pin 8, RX = digital pin 9 + // on the Mega, use other pins instead, since 8 and 9 don't work on the Mega + + SoftwareSerial debug(DEBUG_RX_PIN, DEBUG_TX_PIN); + //#define debug Serial +#endif + +CanSerial* CanSerial::_instance = 0; + +CanSerial* CanSerial::instance() { + if (_instance == 0) + _instance = new CanSerial(); + return _instance; +} + +void CanSerial::init(INT8U defaultCanSpeed, const INT8U clock) { + dbg_begin(LWUART_DEFAULT_BAUD_RATE); // logging through software serial + dbg1("CAN ASCII. Welcome to debug"); + + instance()->LWUARTCanSpeedSelection = defaultCanSpeed; + instance()->LWUARTMcpModuleClock = clock; + instance()->initFunc(); +} + +void CanSerial::setFilter(INT8U (*userFunc)(INT32U)) { + instance()->setFilterFunc(userFunc); +} + +void CanSerial::loop() { + instance()->loopFunc(); +} + +void CanSerial::serialEvent() { + instance()->serialEventFunc(); +} + +void CanSerial::initFunc() { + if (!inputString.reserve(LWUART_INPUT_STRING_BUFFER_SIZE)) { + dbg0("inputString.reserve failed in initFunc. less optimal String work is expected"); + } + // LWUARTAutoStart = true; //todo: read from eeprom + // LWUARTAutoPoll = false; //todo: read from eeprom + // LWUARTTimeStamp = //read from eeprom + // LWUARTMessage[0] = 'Z'; LWUARTMessage[1] = '1'; exec(); + //if (LWUARTAutoStart) { + inputString = "O\0x0D"; + stringComplete = true; + loopFunc(); + //} +} + +void CanSerial::setFilterFunc(INT8U (*userFunc)(INT32U)) { + instance()->userAddressFilterFunc = userFunc; +} + +void CanSerial::loopFunc() { + if (stringComplete) { + int len = inputString.length(); + if (len > 0 && len < LWUART_FRAME_MAX_SIZE) { + strcpy((char*)LWUARTMessage, inputString.c_str()); + exec(); + } + // clear the string: + inputString = ""; + stringComplete = false; + } + if (LWUARTCanChannelMode != LWUART_STATUS_CAN_CLOSED) { + int recv = 0; + while (CAN_MSGAVAIL == checkReceive() && recv++<5) { + dbg0('+'); + if (CAN_OK == receiveSingleFrame()) { + Serial.write(LWUART_CR); + } + } + Serial.flush(); + } +} +void CanSerial::serialEventFunc() { + while (Serial.available()) { + char inChar = (char)Serial.read(); + inputString += inChar; + if (inChar == LWUART_CR) { + stringComplete = true; + } + } +} + +INT8U CanSerial::exec() { + dbg2("Command received:", inputString); + LWUARTLastErr = parseAndRunCommand(); + switch (LWUARTLastErr) { + case LWUART_OK: + Serial.write(LWUART_RET_ASCII_OK); + break; + case LWUART_OK_SMALL: + Serial.write(LWUART_RET_ASCII_OK_SMALL); + Serial.write(LWUART_RET_ASCII_OK); + break; + case LWUART_OK_BIG: + Serial.write(LWUART_RET_ASCII_OK_BIG); + Serial.write(LWUART_RET_ASCII_OK); + break; + case LWUART_ERR_NOT_IMPLEMENTED: + // Choose behavior: will it fail or not when not implemented command comes in. Some can monitors might be affected by this selection. + Serial.write(LWUART_RET_ASCII_ERROR); + //Serial.write(LWUART_RET_ASCII_OK); + break; + default: + Serial.write(LWUART_RET_ASCII_ERROR); + } + return 0; +} + +INT8U CanSerial::parseAndRunCommand() { + INT8U ret = LWUART_OK; + INT8U idx = 0; + INT8U err = 0; + + LWUARTLastErr = LWUART_OK; + + switch (LWUARTMessage[0]) { + case LWUART_CMD_SETUP: + // Sn[CR] Setup with standard CAN bit-rates where n is 0-9. + if (LWUARTCanChannelMode == LWUART_STATUS_CAN_CLOSED) { + idx = HexHelper::parseNibbleWithLimit(LWUARTMessage[1], LWUART_CAN_BAUD_NUM); + LWUARTCanSpeedSelection = LWUARTCanBaudRates[idx]; + } + else { + ret = LWUART_ERR; + } + break; + case LWUART_CMD_SETUP_BTR: + // sxxyy[CR] Setup with BTR0/BTR1 CAN bit-rates where xx and yy is a hex value. + ret = LWUART_ERR; break; + case LWUART_CMD_OPEN: + // O[CR] Open the CAN channel in normal mode (sending & receiving). + if (LWUARTCanChannelMode == LWUART_STATUS_CAN_CLOSED) { + ret = openCanBus(); + if (ret == LWUART_OK) { + LWUARTCanChannelMode = LWUART_STATUS_CAN_OPEN_NORMAL; + } + } + else { + ret = LWUART_ERR; + } + break; + case LWUART_CMD_LISTEN: + // L[CR] Open the CAN channel in listen only mode (receiving). + if (LWUARTCanChannelMode == LWUART_STATUS_CAN_CLOSED) { + ret = openCanBus(); + if (ret == LWUART_OK) { + LWUARTCanChannelMode = LWUART_STATUS_CAN_OPEN_LISTEN; + } + } + else { + ret = LWUART_ERR; + } + break; + case LWUART_CMD_CLOSE: + // C[CR] Close the CAN channel. + if (LWUARTCanChannelMode != LWUART_STATUS_CAN_CLOSED) { + LWUARTCanChannelMode = LWUART_STATUS_CAN_CLOSED; + } + else { + ret = LWUART_ERR; + } + break; + case LWUART_CMD_TX11: + // tiiildd...[CR] Transmit a standard (11bit) CAN frame. + if (LWUARTCanChannelMode == LWUART_STATUS_CAN_OPEN_NORMAL) { + parseCanStdId(); + LWUARTPacketLen = HexHelper::parseNibbleWithLimit(LWUARTMessage[LWUART_OFFSET_STD_PKT_LEN], LWUART_FRAME_MAX_LENGTH + 1); + for (; idx < LWUARTPacketLen; idx++) { + LWUARTBuffer[idx] = HexHelper::parseFullByte(LWUARTMessage[LWUART_OFFSET_STD_PKT_DATA + idx * 2], LWUARTMessage[LWUART_OFFSET_STD_PKT_DATA + idx * 2 + 1]); + } + INT8U mcpErr = sendMsgBuf(LWUARTCanId, 0, 0, LWUARTPacketLen, LWUARTBuffer); + if (mcpErr != CAN_OK) { + ret = LWUART_ERR; + } else if (LWUARTAutoPoll) { + ret = LWUART_OK_SMALL; + } + } + else { + ret = LWUART_ERR; + } + break; + case LWUART_CMD_TX29: + // Tiiiiiiiildd...[CR] Transmit an extended (29bit) CAN frame + if (LWUARTCanChannelMode == LWUART_STATUS_CAN_OPEN_NORMAL) { + parseCanExtId(); + LWUARTPacketLen = HexHelper::parseNibbleWithLimit(LWUARTMessage[LWUART_OFFSET_EXT_PKT_LEN], LWUART_FRAME_MAX_LENGTH + 1); + for (; idx < LWUARTPacketLen; idx++) { + LWUARTBuffer[idx] = HexHelper::parseFullByte(LWUARTMessage[LWUART_OFFSET_EXT_PKT_DATA + idx * 2], LWUARTMessage[LWUART_OFFSET_EXT_PKT_DATA + idx * 2 + 1]); + } + if (CAN_OK != sendMsgBuf(LWUARTCanId, 1, 0, LWUARTPacketLen, LWUARTBuffer)) { + ret = LWUART_ERR; + } else if (LWUARTAutoPoll) { + ret = LWUART_OK_BIG; + } else { + ret = LWUART_OK; + } + } + break; + case LWUART_CMD_RTR11: + // riiil[CR] Transmit an standard RTR (11bit) CAN frame. + if (LWUARTCanChannelMode == LWUART_STATUS_CAN_OPEN_NORMAL) { + parseCanStdId(); + LWUARTPacketLen = HexHelper::parseNibbleWithLimit(LWUARTMessage[LWUART_OFFSET_STD_PKT_LEN], LWUART_FRAME_MAX_LENGTH + 1); + if (CAN_OK != sendMsgBuf(LWUARTCanId, 0, 1, LWUARTPacketLen, LWUARTBuffer)) { + ret = LWUART_ERR; + } + else if (LWUARTAutoPoll) { + ret = LWUART_OK_SMALL; + } + } + else { + ret = LWUART_ERR; + } + break; + case LWUART_CMD_RTR29: + // Riiiiiiiil[CR] Transmit an extended RTR (29bit) CAN frame. + if (LWUARTCanChannelMode == LWUART_STATUS_CAN_OPEN_NORMAL) { + parseCanExtId(); + LWUARTPacketLen = HexHelper::parseNibbleWithLimit(LWUARTMessage[LWUART_OFFSET_EXT_PKT_LEN], LWUART_FRAME_MAX_LENGTH + 1); + if (CAN_OK != sendMsgBuf(LWUARTCanId, 1, 1, LWUARTPacketLen, LWUARTBuffer)) { + ret = LWUART_ERR; + } + else if (LWUARTAutoPoll) { + ret = LWUART_OK_SMALL; // not a typo. strangely CanSerial_v3.pdf tells to return "z[CR]", not "Z[CR]" as in 29bit. todo: check if it is error in pdf??? + } + } else { + ret = LWUART_ERR; + } + break; + case LWUART_CMD_POLL_ONE: + // P[CR] Poll incomming FIFO for CAN frames (single poll) + if (LWUARTCanChannelMode != LWUART_STATUS_CAN_CLOSED && LWUARTAutoPoll == LWUART_AUTOPOLL_OFF) { + if (CAN_MSGAVAIL == checkReceive()) { + ret = receiveSingleFrame(); + } + } else { + ret = LWUART_ERR; + } + break; + case LWUART_CMD_POLL_MANY: + // A[CR] Polls incomming FIFO for CAN frames (all pending frames) + if (LWUARTCanChannelMode != LWUART_STATUS_CAN_CLOSED && LWUARTAutoPoll == LWUART_AUTOPOLL_OFF) { + while (CAN_MSGAVAIL == checkReceive()) { + ret = ret ^ receiveSingleFrame(); + if (ret != CAN_OK) + break; + Serial.write(LWUART_CR); + } + if (ret == CAN_OK) + Serial.print(LWUART_ALL); + } else { + ret = LWUART_ERR; + } + break; + case LWUART_CMD_FLAGS: + // F[CR] Read Status Flags. + // LAWICEL CanSerial and CANUSB have some specific errors which differ from MCP2515/MCP2551 errors. We just return MCP2515 error. + Serial.print(LWUART_FLAG); + if (LWUARTCAN.checkError(&err) == CAN_OK) + err = 0; + HexHelper::printFullByte(err & MCP_EFLG_ERRORMASK); + break; + case LWUART_CMD_AUTOPOLL: + // Xn[CR] Sets Auto Poll/Send ON/OFF for received frames. + if (LWUARTCanChannelMode == LWUART_STATUS_CAN_CLOSED) { + LWUARTAutoPoll = (LWUARTMessage[1] == LWUART_ON_ONE) ? LWUART_AUTOPOLL_ON : LWUART_AUTOPOLL_OFF; + //todo: save to eeprom + } else { + ret = LWUART_ERR; + } + break; + case LWUART_CMD_FILTER: + // Wn[CR] Filter mode setting. By default CanSerial works in dual filter mode (0) and is backwards compatible with previous CanSerial versions. + ret = LWUART_ERR_NOT_IMPLEMENTED; break; + case LWUART_CMD_ACC_CODE: + // Mxxxxxxxx[CR] Sets Acceptance Code Register (ACn Register of SJA1000). // we use MCP2515, + ret = LWUART_ERR_NOT_IMPLEMENTED; break; + case LWUART_CMD_ACC_MASK: + // mxxxxxxxx[CR] Sets Acceptance Mask Register (AMn Register of SJA1000). + ret = LWUART_ERR_NOT_IMPLEMENTED; break; + case LWUART_CMD_UART: + // Un[CR] Setup UART with a new baud rate where n is 0-6. + idx = HexHelper::parseNibbleWithLimit(LWUARTMessage[1], LWUART_UART_BAUD_NUM); + Serial.begin(LWUARTSerialBaudRates[idx]); + break; + case LWUART_CMD_VERSION1: + case LWUART_CMD_VERSION2: + // V[CR] Get Version number of both CanSerial hardware and software + Serial.print(LWUART_LAWICEL_VERSION_STR); + break; + case LWUART_CMD_SERIAL: + // N[CR] Get Serial number of the CanSerial. + Serial.print(LWUART_LAWICEL_SERIAL_NUM); + break; + case LWUART_CMD_TIMESTAMP: + // Zn[CR] Sets Time Stamp ON/OFF for received frames only. Z0 - OFF, Z1 - Lawicel's timestamp 2 bytes, Z2 - arduino timestamp 4 bytes. + if (LWUARTCanChannelMode == LWUART_STATUS_CAN_CLOSED) { + // LWUARTTimeStamp = (LWUARTMessage[1] == LWUART_ON_ONE); + if (LWUARTMessage[1] == LWUART_ON_ONE) { + LWUARTTimeStamp = LWUART_TIMESTAMP_ON_NORMAL; + } + else if (LWUARTMessage[1] == LWUART_ON_TWO) { + LWUARTTimeStamp = LWUART_TIMESTAMP_ON_EXTENDED; + } + else { + LWUARTTimeStamp = LWUART_TIMESTAMP_OFF; + } + } + else { + ret = LWUART_ERR; + } + break; + case LWUART_CMD_AUTOSTART: + // Qn[CR] Auto Startup feature (from power on). + if (LWUARTCanChannelMode != LWUART_STATUS_CAN_CLOSED) { + if (LWUARTMessage[1] == LWUART_ON_ONE) { + LWUARTAutoStart = LWUART_AUTOSTART_ON_NORMAL; + } + else if (LWUARTMessage[1] == LWUART_ON_TWO) { + LWUARTAutoStart = LWUART_AUTOSTART_ON_LISTEN; + } + else { + LWUARTAutoStart = LWUART_AUTOSTART_OFF; + } + //todo: save to eeprom + } + else { + ret = LWUART_ERR; + } + break; + default: + ret = LWUART_ERR_UNKNOWN_CMD; + } + + return ret; +} + +INT8U CanSerial::checkReceive() { +#ifndef _MCP_FAKE_MODE_ + return LWUARTCAN.checkReceive(); +#else + return CAN_MSGAVAIL; +#endif +} + +INT8U CanSerial::readMsgBufID(INT32U *ID, INT8U *len, INT8U buf[]) { +#ifndef _MCP_FAKE_MODE_ + return LWUARTCAN.readMsgBufID(ID, len, buf); +#else + *ID = random(0x100, 0x110); + *len = 4; + buf[0] = random(0x01, 0x10); + buf[1] = random(0xa1, 0xf0); + buf[2] = 0x00; + buf[3] = 0x00; + return CAN_OK; +#endif +} + + + +INT8U CanSerial::receiveSingleFrame() { + INT8U ret = LWUART_OK; + INT8U idx = 0; + if (CAN_OK == readMsgBufID(&LWUARTCanId, &LWUARTPacketLen, LWUARTBuffer)) { + if (LWUARTCanId > 0x1FFFFFFF) { + ret = LWUART_ERR; // address if totally wrong + } + else if (checkPassFilter(LWUARTCanId)) {// do we want to skip some addresses? + if (isExtendedFrame()) { + Serial.print(LWUART_TR29); + HexHelper::printFullByte(HIGH_BYTE(HIGH_WORD(LWUARTCanId))); + HexHelper::printFullByte(LOW_BYTE(HIGH_WORD(LWUARTCanId))); + HexHelper::printFullByte(HIGH_BYTE(LOW_WORD(LWUARTCanId))); + HexHelper::printFullByte(LOW_BYTE(LOW_WORD(LWUARTCanId))); + } + else { + Serial.print(LWUART_TR11); + HexHelper::printNibble(HIGH_BYTE(LOW_WORD(LWUARTCanId))); + HexHelper::printFullByte(LOW_BYTE(LOW_WORD(LWUARTCanId))); + } + //write data len + HexHelper::printNibble(LWUARTPacketLen); + //write data + for (idx = 0; idx < LWUARTPacketLen; idx++) { + HexHelper::printFullByte(LWUARTBuffer[idx]); + } + //write timestamp if needed + if (LWUARTTimeStamp != LWUART_TIMESTAMP_OFF) { + INT32U time = millis(); + if (LWUARTTimeStamp == LWUART_TIMESTAMP_ON_NORMAL) { + // standard LAWICEL protocol. two bytes. + time %= 60000; + } else { + // non standard protocol - 4 bytes timestamp + HexHelper::printFullByte(HIGH_BYTE(HIGH_WORD(time))); + HexHelper::printFullByte(LOW_BYTE(HIGH_WORD(time))); + } + HexHelper::printFullByte(HIGH_BYTE(LOW_WORD(time))); + HexHelper::printFullByte(LOW_BYTE(LOW_WORD(time))); + } + } + } + else { + ret = LWUART_ERR; + } + return ret; +} + + +INT8U CanSerial::isExtendedFrame() { +#ifndef _MCP_FAKE_MODE_ + return LWUARTCAN.isExtendedFrame(); +#else + return LWUARTCanId > 0x7FF ? 1 : 0; //simple hack for fake mode +#endif +} + + +INT8U CanSerial::checkPassFilter(INT32U addr) { + if (userAddressFilterFunc == 0) + return LWUART_FILTER_PROCESS; + + return (*userAddressFilterFunc)(addr); +} + +INT8U CanSerial::openCanBus() { + INT8U ret = LWUART_OK; +#ifndef _MCP_FAKE_MODE_ + if (CAN_OK != LWUARTCAN.begin(LWUARTCanSpeedSelection, LWUARTMcpModuleClock)) + ret = LWUART_ERR; +#endif + return ret; +} + + +INT8U CanSerial::sendMsgBuf(INT32U id, INT8U ext, INT8U rtr, INT8U len, INT8U *buf) { +#ifndef _MCP_FAKE_MODE_ + return LWUARTCAN.sendMsgBuf(id, ext, rtr, len, buf); +#else + Serial.print("= '0' && hex <= '9') { + ret = hex - '0'; + } else if (hex >= 'a' && hex <= 'f') { + ret = hex - 'a' + 10; + } else if (hex >= 'A' && hex <= 'F') { + ret = hex - 'A' + 10; + } // else error, return 0 + return ret; +} + +INT8U HexHelper::parseFullByte(INT8U H, INT8U L) { + return (parseNibble(H) << 4) + parseNibble(L); +} + +INT8U HexHelper::parseNibbleWithLimit(INT8U hex, INT8U limit) { + INT8U ret = parseNibble(hex); + if (ret < limit) + return ret; + else + return 0; +} diff --git a/can-serial.h b/can-serial.h new file mode 100644 index 0000000..8c8edc9 --- /dev/null +++ b/can-serial.h @@ -0,0 +1,244 @@ +/***************************************************************************************** +* This is implementation of CAN BUS ASCII protocol based on LAWICEL v1.3 serial protocol +* of CanSerial/CANUSB device (http://www.CanSerial.com/docs/CanSerial_v3.pdf) +* +* Made for Arduino with Seeduino/ElecFreaks CAN BUS Shield based on MCP2515 +* +* Copyright (C) 2015 Anton Viktorov +* https://github.com/latonita/can-ascii +* +* This library is free software. You may use/redistribute it under The MIT License terms. +* +*****************************************************************************************/ + + +#ifndef _CAN_SERIAL_H_ +#define _CAN_SERIAL_H_ + +#include "mcp_can.h" + + +#define LOGGING_ENABLED + +#ifdef LOGGING_ENABLED +#include "SoftwareSerial.h" +#define dbg_begin(x) debug.begin(x) +#define dbg0(x) debug.print(x) +#define dbg1(x) debug.println(x) +#define dbg2(x,y) debug.print(x); debug.println(y) +#define dbgH(x) debug.print(x,HEX) +#define DEBUG_RX_PIN 8 +#define DEBUG_TX_PIN 9 +#else +#define dbg_begin(x) +#define dbg0(x) +#define dbg1(x) +#define dbg2(x,y) +#define dbgH(x) +#endif + + + +#define LWUART_LAWICEL_VERSION_STR "V1013" +#define LWUART_LAWICEL_SERIAL_NUM "NA123" +#define LWUART_CAN_BUS_SHIELD_CS_PIN 10 + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Commands not supported/not implemented: +// s, W, M, m, U +// +// Commands modified: +// S - supports not declared 83.3 rate straight away (S9) +// refer to https://github.com/latonita/CAN_BUS_Shield fork with 83.3 support, easy to add. +// F - returns MCP2515 error flags +// Z - extra Z2 option enables 4 byte timestamp vs standard 2 byte (60000ms max) +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// CODE SUPPORTED SYNTAX DESCRIPTION +// +#define LWUART_CMD_SETUP 'S' // YES+ Sn[CR] Setup with standard CAN bit-rates where n is 0-8. + // S0 10Kbit S4 125Kbit S8 1Mbit + // S1 20Kbit S5 250Kbit S9 83.3Kbit + // S2 50Kbit S6 500Kbit + // S3 100Kbit S7 800Kbit +#define LWUART_CMD_SETUP_BTR 's' // - sxxyy[CR] Setup with BTR0/BTR1 CAN bit-rates where xx and yy is a hex value. +#define LWUART_CMD_OPEN 'O' // YES O[CR] Open the CAN channel in normal mode (sending & receiving). +#define LWUART_CMD_LISTEN 'L' // YES L[CR] Open the CAN channel in listen only mode (receiving). +#define LWUART_CMD_CLOSE 'C' // YES C[CR] Close the CAN channel. +#define LWUART_CMD_TX11 't' // YES tiiildd...[CR] Transmit a standard (11bit) CAN frame. +#define LWUART_CMD_TX29 'T' // YES Tiiiiiiiildd...[CR] Transmit an extended (29bit) CAN frame +#define LWUART_CMD_RTR11 'r' // YES riiil[CR] Transmit an standard RTR (11bit) CAN frame. +#define LWUART_CMD_RTR29 'R' // YES Riiiiiiiil[CR] Transmit an extended RTR (29bit) CAN frame. +#define LWUART_CMD_POLL_ONE 'P' // YES P[CR] Poll incomming FIFO for CAN frames (single poll) +#define LWUART_CMD_POLL_MANY 'A' // YES A[CR] Polls incomming FIFO for CAN frames (all pending frames) +#define LWUART_CMD_FLAGS 'F' // YES+ F[CR] Read Status Flags. +#define LWUART_CMD_AUTOPOLL 'X' // YES Xn[CR] Sets Auto Poll/Send ON/OFF for received frames. +#define LWUART_CMD_FILTER 'W' // - Wn[CR] Filter mode setting. By default CanSerial works in dual filter mode (0) and is backwards compatible with previous CanSerial versions. +#define LWUART_CMD_ACC_CODE 'M' // - Mxxxxxxxx[CR] Sets Acceptance Code Register (ACn Register of SJA1000). // we use MCP2515, not supported +#define LWUART_CMD_ACC_MASK 'm' // - mxxxxxxxx[CR] Sets Acceptance Mask Register (AMn Register of SJA1000). // we use MCP2515, not supported +#define LWUART_CMD_UART 'U' // YES Un[CR] Setup UART with a new baud rate where n is 0-6. +#define LWUART_CMD_VERSION1 'V' // YES v[CR] Get Version number of both CanSerial hardware and software +#define LWUART_CMD_VERSION2 'v' // YES V[CR] Get Version number of both CanSerial hardware and software +#define LWUART_CMD_SERIAL 'N' // YES N[CR] Get Serial number of the CanSerial. +#define LWUART_CMD_TIMESTAMP 'Z' // YES+ Zn[CR] Sets Time Stamp ON/OFF for received frames only. EXTENSION to LAWICEL: Z2 - millis() timestamp w/o standard 60000ms cycle +#define LWUART_CMD_AUTOSTART 'Q' // YES todo Qn[CR] Auto Startup feature (from power on). + +#define LOW_BYTE(x) ((unsigned char)((x)&0xFF)) +#define HIGH_BYTE(x) ((unsigned char)(((x)>>8)&0xFF)) +#define LOW_WORD(x) ((unsigned short)((x)&0xFFFF)) +#define HIGH_WORD(x) ((unsigned short)(((x)>>16)&0xFFFF)) + +#ifndef INT32U +#define INT32U unsigned long +#endif + +#ifndef INT16U +#define INT16U word +#endif + +#ifndef INT8U +#define INT8U byte +#endif + +#define LWUART_OK 0x00 +#define LWUART_OK_SMALL 0x01 +#define LWUART_OK_BIG 0x02 +#define LWUART_ERR 0x03 +#define LWUART_ERR_NOT_IMPLEMENTED 0x04 +#define LWUART_ERR_UNKNOWN_CMD 0x05 + +#define LWUART_FILTER_SKIP 0x00 +#define LWUART_FILTER_PROCESS 0x01 + +//#define LWUART_IS_OK(x) ((x)==LWUART_OK ||(x)==LWUART_OK_NEW ? TRUE : FALSE) + +#define LWUART_CR '\r' +#define LWUART_ALL 'A' +#define LWUART_FLAG 'F' +#define LWUART_TR11 't' +#define LWUART_TR29 'T' + +#define LWUART_RET_ASCII_OK 0x0D +#define LWUART_RET_ASCII_ERROR 0x07 +#define LWUART_RET_ASCII_OK_SMALL 'z' +#define LWUART_RET_ASCII_OK_BIG 'Z' + +#define LWUART_STATUS_CAN_CLOSED 0x00 +#define LWUART_STATUS_CAN_OPEN_NORMAL 0x01 +#define LWUART_STATUS_CAN_OPEN_LISTEN 0x01 + +#define LWUART_FRAME_MAX_LENGTH 0x08 +#define LWUART_FRAME_MAX_SIZE (sizeof("Tiiiiiiiildddddddddddddddd\r")+1) + +#define LWUART_INPUT_STRING_BUFFER_SIZE 200 + +#define LWUART_OFF '0' +#define LWUART_ON_ONE '1' +#define LWUART_ON_TWO '2' + +#define LWUART_AUTOPOLL_OFF 0x00 +#define LWUART_AUTOPOLL_ON 0x01 + +#define LWUART_AUTOSTART_OFF 0x00 +#define LWUART_AUTOSTART_ON_NORMAL 0x01 +#define LWUART_AUTOSTART_ON_LISTEN 0x02 + +#define LWUART_TIMESTAMP_OFF 0x00 +#define LWUART_TIMESTAMP_ON_NORMAL 0x01 +#define LWUART_TIMESTAMP_ON_EXTENDED 0x02 + +#define LWUART_OFFSET_STD_PKT_LEN 0x04 +#define LWUART_OFFSET_STD_PKT_DATA 0x05 +#define LWUART_OFFSET_EXT_PKT_LEN 0x09 +#define LWUART_OFFSET_EXT_PKT_DATA 0x0A + + +#define LWUART_DEFAULT_BAUD_RATE 115200 +#define LWUART_DEFAULT_CAN_RATE CAN_500KBPS +#define LWUART_DEFAULT_CLOCK_FREQ MCP_16MHz + +#define LWUART_CAN_BAUD_NUM 0x0a +#define LWUART_UART_BAUD_NUM 0x07 + + +const INT32U LWUARTSerialBaudRates[] //PROGMEM += { 230400, 115200, 57600, 38400, 19200, 9600, 2400 }; + +const INT32U LWUARTCanBaudRates[] //PROGMEM += { CAN_10KBPS, CAN_20KBPS, CAN_50KBPS, CAN_100KBPS, CAN_125KBPS, CAN_250KBPS, CAN_500KBPS, CAN_500KBPS /*CAN_800KBPS*/, CAN_1000KBPS, CAN_83K3BPS }; + +class CanSerial +{ +public: + static void init(INT8U defaultCanSpeed = LWUART_DEFAULT_CAN_RATE, const INT8U clock = LWUART_DEFAULT_CLOCK_FREQ); + static void setFilter(INT8U (*userFunc)(INT32U)); + static void loop(); + static void serialEvent(); + +private: + static CanSerial* _instance; + static CanSerial* instance(); + + void initFunc(); + void setFilterFunc(INT8U (*userFunc)(INT32U)); + void loopFunc(); + void serialEventFunc(); + + INT8U (*userAddressFilterFunc)(INT32U addr) = 0; + + MCP_CAN LWUARTCAN = MCP_CAN(LWUART_CAN_BUS_SHIELD_CS_PIN); + + INT8U LWUARTCanSpeedSelection = CAN_83K3BPS; + INT8U LWUARTMcpModuleClock = MCP_16MHz; + INT8U LWUARTCanChannelMode = LWUART_STATUS_CAN_CLOSED; + INT8U LWUARTLastErr = LWUART_OK; + + INT8U LWUARTAutoStart = LWUART_AUTOSTART_OFF; + INT8U LWUARTAutoPoll = LWUART_AUTOPOLL_OFF; + INT8U LWUARTTimeStamp = LWUART_TIMESTAMP_OFF; + + INT32U LWUARTCanId = 0; + + INT8U LWUARTBuffer[8]; + INT8U LWUARTPacketLen = 0; + + INT8U LWUARTMessage[LWUART_FRAME_MAX_SIZE]; + + String inputString = ""; // a string to hold incoming data + boolean stringComplete = false; // whether the string is complete + + INT8U parseAndRunCommand(); + INT8U exec(); + + INT8U checkReceive(); + INT8U readMsgBufID(INT32U *ID, INT8U *len, INT8U buf[]); + INT8U receiveSingleFrame(); + INT8U isExtendedFrame(); + INT8U checkPassFilter(INT32U addr); + INT8U openCanBus(); + + INT8U sendMsgBuf(INT32U id, INT8U ext, INT8U rtr, INT8U len, INT8U *buf); + + void parseCanStdId(); + void parseCanExtId(); +}; + +class HexHelper { +public: + static void printFullByte(INT8U b); + static void printNibble(INT8U b); + + static INT8U parseNibble(INT8U hex); + static INT8U parseFullByte(INT8U H, INT8U L); + static INT8U parseNibbleWithLimit(INT8U hex, INT8U limit); +}; + +class CanSerialFake : CanSerial { + +}; + + +#endif diff --git a/examples/canbus-monitor/README.md b/examples/canbus-monitor/README.md new file mode 100644 index 0000000..e49b2be --- /dev/null +++ b/examples/canbus-monitor/README.md @@ -0,0 +1,62 @@ +# arduino-canbus-monitor [![Build Status](https://api.travis-ci.org/latonita/arduino-canbus-monitor.svg?branch=master)](https://travis-ci.org/latonita/arduino-canbus-monitor) [![Coverity Scan](https://scan.coverity.com/projects/11684/badge.svg)](https://scan.coverity.com/projects/latonita-arduino-canbus-monitor) [![Analytics](https://ga-beacon.appspot.com/UA-99380399-1/welcome-page)](https://github.com/igrigorik/ga-beacon) + +CAN BUS monitoring software based on Arduino with Seeduino/ElecFreaks CAN BUS shield based on MCP2515 (Numerous other MCP2515 based CAN BUS modules from ebay and aliexpress work well to). + +This software implements CAN ASCII / Serial CAN / SLCAN protocol compatible with Lawicel CAN232/CANUSB. + +As for PC counterpart software I personally used and can recommend two tools: + +1) [Windows] CANHacker tool v.2.00.01 (by fuchs) to sniff and visualize data on the bus. You can download CANHacker tool from this forum page: http://www.canhack.net/viewforum.php?f=25&sid=ac01d465f19e088cb160cab630561607 + +2) [Windows] CAN-COOL (by MHS Elektronik), open source, but unfortunaly available only in German. Download link: http://www.mhs-elektronik.de/index.php?module=content&action=show&page=can_cool (Make sure you select RS232 and SL-CAN protocol and then click hardware bus reset icon on a toolbar) + +3) [Linux] Please dig into direction of SLCAN/SocketCAN, but start from https://github.com/linux-can/can-utils + +This monitor uses CAN BUS library forked from https://github.com/Seeed-Studio/CAN_BUS_Shield. + +Copyright (C) 2015,2016 Anton Viktorov + +You can buy me a beer if you like the tool :o) [![Donate](https://www.paypal.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=4JPDVHYWUY3LW) + + +See protocol definition here http://www.can232.com/docs/can232_v3.pdf and here http://www.can232.com/docs/canusb_manual.pdf + +Commands not supported/not implemented: +- s, W, M, m, U. + +Commands modified: +- S - supports not declared 83.3 rate +- F - returns MCP2515 error flags +- Z - extra Z2 option enables 4 byte timestamp vs standard 2 byte (60000ms max) + +``` +CMD | IMPLEMENTED | SYNTAX | DESCRIPTION +------------------------------------------------------------------------------------------------------------ +'S' | YES+ | Sn[CR] Setup with standard CAN bit-rates where n is 0-8. + | | S0 10Kbit S4 125Kbit S8 1Mbit + | | S1 20Kbit S5 250Kbit S9 83.3Kbit + | | S2 50Kbit S6 500Kbit + | | S3 100Kbit S7 800Kbit +'s' | - | sxxyy[CR] Setup with BTR0/BTR1 CAN bit-rates where xx and yy is a hex value. +'O' | YES | O[CR] Open the CAN channel in normal mode (sending & receiving). +'L' | YES | L[CR] Open the CAN channel in listen only mode (receiving). +'C' | YES | C[CR] Close the CAN channel. +'t' | YES | tiiildd...[CR] Transmit a standard (11bit) CAN frame. +'T' | YES | Tiiiiiiiildd...[CR] Transmit an extended (29bit) CAN frame +'r' | YES | riiil[CR] Transmit an standard RTR (11bit) CAN frame. +'R' | YES | Riiiiiiiil[CR] Transmit an extended RTR (29bit) CAN frame. +'P' | YES | P[CR] Poll incomming FIFO for CAN frames (single poll) +'A' | YES | A[CR] Polls incomming FIFO for CAN frames (all pending frames) +'F' | YES+ | F[CR] Read Status Flags. +'X' | YES | Xn[CR] Sets Auto Poll/Send ON/OFF for received frames. +'W' | - | Wn[CR] Filter mode setting. By default CAN232 works in dual filter mode (0) and is backwards compatible with previous CAN232 versions. +'M' | - | Mxxxxxxxx[CR] Sets Acceptance Code Register (ACn Register of SJA1000). // we use MCP2515, not supported +'m' | - | mxxxxxxxx[CR] Sets Acceptance Mask Register (AMn Register of SJA1000). // we use MCP2515, not supported +'U' | YES | Un[CR] Setup UART with a new baud rate where n is 0-6. +'V' | YES | v[CR] Get Version number of both CAN232 hardware and software +'v' | YES | V[CR] Get Version number of both CAN232 hardware and software +'N' | YES | N[CR] Get Serial number of the CAN232. +'Z' | YES+ | Zn[CR] Sets Time Stamp ON/OFF for received frames only. EXTENSION to LAWICEL: Z2 - millis() timestamp w/o standard 60000ms cycle +'Q' | YES todo | Qn[CR] Auto Startup feature (from power on). + +``` diff --git a/examples/canbus-monitor/arduino-canbus-monitor.ino b/examples/canbus-monitor/arduino-canbus-monitor.ino new file mode 100644 index 0000000..149137a --- /dev/null +++ b/examples/canbus-monitor/arduino-canbus-monitor.ino @@ -0,0 +1,68 @@ +/***************************************************************************************** +* This is implementation of CAN BUS ASCII protocol based on LAWICEL v1.3 serial protocol +* of CAN232/CANUSB device (http://www.can232.com/docs/can232_v3.pdf) +* +* Made for Arduino with Seeduino/ElecFreaks CAN BUS Shield based on MCP2515 +* +* Copyright (C) 2015 Anton Viktorov +* https://github.com/latonita/arduino-canbus-monitor +* +* This library is free software. You may use/redistribute it under The MIT License terms. +* +*****************************************************************************************/ + +#include +#include "mcp_can.h" +#include "can-serial.h" +void setup() { + Serial.begin(LWUART_DEFAULT_BAUD_RATE); // default COM baud rate is 115200. + + // Can232::init (RATE, CLOCK) + // Rates: CAN_10KBPS, CAN_20KBPS, CAN_50KBPS, CAN_100KBPS, CAN_125KBPS, CAN_250KBPS, CAN_500KBPS, CAN_500KBPS, CAN_1000KBPS, CAN_83K3BPS + // Default is CAN_83K3BPS ;))))))))) + // Clock: MCP_16MHz or MCP_8MHz. + // Default is MCP_16MHz. Please note, not all CAN speeds supported. check big switch in mcp_can.cpp + // defaults can be changed in mcp_can.h + +// Can232::init(); // rate and clock = LW232_DEFAULT_CAN_RATE and LW232_DEFAULT_CLOCK_FREQ +// Can232::init(CAN_125KBPS); // rate = 125, clock = LW232_DEFAULT_CLOCK_FREQ + CanSerial::init(CAN_125KBPS, MCP_16MHz); // set default rate you need here and clock frequency of CAN shield. Typically it is 16MHz, but on some MCP2515 + TJA1050 it is 8Mhz + + + // optional custom packet filter to reduce number of messages comingh through to canhacker + // Can232::setFilter(myCustomAddressFilter); +} + +INT8U myCustomAddressFilter(INT32U addr) { + INT8U ret = LWUART_FILTER_SKIP; //LW232_FILTER_PROCESS or LW232_FILTER_SKIP + switch(addr) { + // case 0x01b: //VIN + // case 0x1C8: //lights + // case 0x2C0: // pedals + case 0x3d0: // sound vol, treb.. + ret = LWUART_FILTER_PROCESS; + // case 0x000: // ? + // case 0x003: //shifter + // case 0x015: // dor open close affects this as well + // case 0x02c: // ??? + // ret = 0; + // break; + // case 0x002: + // case 0x1a7: //fuel cons or some other + // ret = 1; + // break; + // default: + // ret = 0; + } + + return ret; +} + +void loop() { + CanSerial::loop(); +} + +void serialEvent() { + CanSerial::serialEvent(); +} +