I just got the OBD-II CAN-BUS Development Kit from Seeed Studio. I connected the CAN module to the OBD cable (CAN High and CAN Low) and to an Arduino Uno (Pins D2 and D3 for RX and TX, respectively) and plugged it into my car running the "recv" example program using this library: GitHub - Longan-Labs/Serial_CAN_Arduino
I was able to see a flow of input in the serial monitor displaying PIDs and their data. I'll post the original example code just so you can see it here.
// RECV EXAMPLE OF SERIAL CAN MODULE
// unsigned char recv(unsigned long *id, uchar *buf);
// SUPPORT: joney.sui@longan-labs.cc
#include <Serial_CAN_Module.h>
#include <SoftwareSerial.h>
Serial_CAN can;
#define can_tx 2 // tx of serial can module connect to D2
#define can_rx 3 // rx of serial can module connect to D3
void setup()
{
Serial.begin(9600);
can.begin(can_tx, can_rx, 9600); // tx, rx
Serial.println("begin");
}
unsigned long id = 0;
unsigned char dta[8];
// send(unsigned long id, byte ext, byte rtrBit, byte len, const byte *buf);
void loop()
{
if(can.recv(&id, dta))
{
Serial.print("GET DATA FROM ID: ");
Serial.println(id);
for(int i=0; i<8; i++)
{
Serial.print("0x");
Serial.print(dta[i], HEX);
Serial.print('\t');
}
Serial.println();
}
}
// END FILE
What I am asking help for is trying to read the RPM of the car specifically. I looked on the Arduino forums about this and couldn't find any threads myself, so if you know of one please refer me to that!
I tried adding an if() statement to only print to the serial monitor when the id was 12 (Looked on the OBD-II PID Wikipedia page, which told me 12 is the PID for the engine RPM), but ended up getting no data at all. I never tried connecting the P1 resistor on the CAN module. I saw some articles that said I might need to but I'm not sure how to tell if I do.
If you need any more information let me know,
Thanks!
@wildbill Good point. I'll send a picture of the serial monitor, but I'm not sure if it's valid data. Sorry you can't see the baud rate on the bottom. I have to leave and don't have time to get new serial output but I think that screenshot should be fine
Yes, I can try that. I have tried it before but I don't know what it wants me to enter into the serial monitor. I've tried entering different combinations of codes, like "0x0C", "0C", "C", and also trying to add "\n" to the end because it says something about that at the top of the sketch. But just for clarification could you give me an example of a valid input I could give through the serial monitor?
From the look of the code I would just type 'c' or 'C' (no single quotes) followed by ; make sure the termination character "Newline" is selected at the bottom of the serial monitor.
I uploaded that code to my Uno, and it's acting strange. When the Arduino was not connected to my car, I got the serial output,
"set mask ok
set filt ok
begin"
But when I plugged it into my car and ran the code, I got no serial output at all. It didn't even get to the "Serial.println("begin"); line.
However, I am able to make the code run when I run the "factory_setting" example from the library (Really just to reset the baud rate) and then run the "obd_demo" example you provided with these lines commented out:
if(can.canRate(CAN_RATE_500))
{
Serial.println("set can rate ok");
}
else
{
Serial.println("set can rate fail");
}
Once I do that, I get the same output as before,
"set mask ok
set filt ok
begin"
But now when I try to type 'c' as you suggested, I get this serial output,
"SEND PID: 0xC"
But nothing else.
I will attach a screenshot of that last output with the code commented out so you can see it instead of just my bad quotes.
I can't right now, but would it be worth trying to change the value for the "STANDARD_CAN_11BIT" variable at the top from 1 to 0?
I'm connecting to a 2016 Scion Tc. I've tried looking for documentation online but couldn't find any that show the baud rate of the CAN in my car. I assume whatever baud rate is set in the "factory_setting" example is the same as what my car uses because whenever I run factory setting, then I'm able to get data from my car using the "recv" example. In the comments at the top of the factory setting example, it says it sets the baud rate to 500Kb. But then why would the example only work if I comment out those lines that set the baud rate to 500Kb??
I also want to clear up some confusion I have concerning the termination resistor on the CAN module that's connected to my Arduino. Do I need to use that resistor? Or is it not important?
I also just remembered there's the variable at the top of the demo program. I haven't tried to change it from 1 to 0. Maybe I should try changing that and see what it does. If you have any insight on that let me know because I don't understand what it really does at all.
A lot of what you are asking is situation dependant,
If you are connecting to an operational CAN then no, you do not need to add a resistor, the network is already setup and working.
Different manufacturers implement different network architecture, you should consult the factory repair manual for network layout, speeds and resistor locations.
As for rpm, it’s a process of elimination, code with a ton of if()s and narrow down what you’ve got.
I’m working on a VW right now, seemed impossible at first but once I cracked a few things it got much easier, eliminate basic things first, doors, lights, temp, key position, radio, you’ll run into confusing check sums, data that counts constantly, etc etc but eventually the list of IDs and Bytes you’ve not identified will dwindle.
I’m using Arduino, MCP2515 and mcp2515 lib.
8 and 16 MHz version work fine with nano and mega for me, no filtering, no interrupts, just if() statements.
Hope that helps a bit
Good luck!
Thanks for the reply. I figured it might take a lot of tweaking. I have a question about the IDs though. When I'm running the "recv" example and reading data from the car, the serial monitor outputs an ID and the an 8 bits of hex information paired with that ID. I have referred to the OBD-II IDs Wikipedia page before and it says that the ID for RPM is 12. Could I not just use a single if() statement to check if the Arduino is reading an ID with the value 12 and then take in that data for RPM? But I think I said earlier that I tried that and that ID (12) doesn't come up ever.
Also, if you've had some luck, and if you're willing, I would love to look at some of your code to see how you went about doing things.
I think I thought this would be a lot easier before I started haha. But I'm open to learning. There's a link someone posted earlier in this thread referring to reading the hex data. So I'll look into that because I still need to know how to interpret that data into number values, to read the rpm or speed for example.
All cars after 2008 run a high and low canbus. High being 500kbps and low being 33kbps. The oddities lie in what extra buses are running. For standard OBD2 data such as DTC codes the high speed can is used. For all the extras such as the air conditioning and stereo stuff is run on the low speed can. GM came out with a GMLAN in 2005 and all the lower speed busses now run off 33kbps and all engine controls and such on high.
Im working on the same project sort of. Mine is going to be the input for a carputer dash infotainment system. I am using an Arduino UNO with a spark fun CANBUS shield. Im reading the data from the board pins and not the serial port. My truck uses the GMLAN and its on pin 1 of the OBD2 port. To program the serial port to read on a pin other than one is just not what I want to do..lol So I hav used the onboard headers to read HIGH/LOW can bus.
Here is the code for mine:
// This code creates the interface between the car
// and the canSniffer_GUI application. If the RANDOM_CAN
// define is set to 1, this code is generating random
// CAN packets in order to test the higher level code.
// The received packets will be echoed back. If the
// RANDOM_CAN define is set to 0, the CAN_SPEED define
// has to match the speed of the desired CAN channel in
// order to receive and transfer from and to the CAN bus.
// Serial speed is 250000baud <- might need to be increased.
// Required arduino packages:
// - CAN by Sandeep Mistry (GitHub - sandeepmistry/arduino-CAN: An Arduino library for sending and receiving data using CAN bus.)
// Required modifications:
// - MCP2515.h: 16e6 clock frequency reduced to 8e6 (depending on MCP2515 clock)
// - MCP2515.cpp: extend CNF_MAPPER with your desired CAN speeds
//------------------------------------------------------------------------------ #include <CAN.h>
const char SEPARATOR = ',';
const char TERMINATOR = '\n';
const char RXBUF_LEN = 100;
//------------------------------------------------------------------------------
// Printing a packet to serial
void printHex(int num) {
if (num < 0x10) {
Serial.print("0");
}
Serial.print(num, HEX);
}
void printPacket(packet_t * packet) {
// packet format (hex string): [ID],[RTR],[IDE],[DATABYTES 0..8B]\n
// example: 014A,00,00,1A002B003C004D\n
printHex(packet->id);
Serial.print(SEPARATOR);
printHex(packet->rtr);
Serial.print(SEPARATOR);
printHex(packet->ide);
Serial.print(SEPARATOR);
// DLC is determinded by number of data bytes, format: [00]
for (int i = 0; i < packet->dlc; i++) {
printHex(packet->dataArray*);*
}*
Serial.print(TERMINATOR);* } //------------------------------------------------------------------------------ // CAN packet simulator void CANsimulate(void) {
packet_t txPacket;*
int sampleIdList[] = {0x110, 0x115, 0x23A, 0x257, 0x501, 0x601, 0x621};*
int idIndex = random (sizeof(sampleIdList) / sizeof(sampleIdList[0]));*
if (changeByte == 0) {*
_ sampleData = random(256);_ * }* txPacket.dataArray = sampleData*;* * }* * printPacket(&txPacket);* } //------------------------------------------------------------------------------ // CAN RX, TX void onCANReceive(int packetSize) { * // received a CAN packet* * packet_t rxPacket; _ rxPacket.id = CAN.packetId(); rxPacket.rtr = CAN.packetRtr() ? 1 : 0; rxPacket.ide = CAN.packetExtended() ? 1 : 0; rxPacket.dlc = CAN.packetDlc(); byte i = 0; while (CAN.available()) { rxPacket.dataArray[i++] = CAN.read(); if (i >= (sizeof(rxPacket.dataArray) / (sizeof(rxPacket.dataArray[0])))) { break; } } printPacket(&rxPacket); } void sendPacketToCan(packet_t * packet) { for (int retries = 10; retries > 0; retries--) { bool rtr = packet->rtr ? true : false; CAN.beginPacket(packet->id, packet->dlc, rtr); CAN.write(packet->dataArray, packet->dlc); if (CAN.endPacket()) { // success* * break; } else if (retries <= 1) { return; } } } //------------------------------------------------------------------------------ // Serial parser* char getNum(char c) { * if (c >= '0' && c <= '9') { return c - '0'; } if (c >= 'a' && c <= 'f') { return c - 'a' + 10; } if (c >= 'A' && c <= 'F') { return c - 'A' + 10; } return 0; } char * strToHex(char * str, byte * hexArray, byte * len) { byte ptr = hexArray; char * idx; for (idx = str ; idx != SEPARATOR && idx != TERMINATOR; ++idx, ++ptr ) { ptr = (getNum( idx++ ) << 4) + getNum( idx ); } len = ptr - hexArray; return idx; }_ void rxParse(char * buf, int len) { packet_t rxPacket; _ char * ptr = buf; * // All elements have to have leading zero! // ID* * byte idTempArray[4], tempLen; ptr = strToHex(ptr, idTempArray, &tempLen); rxPacket.id = 0; for (int i = 0; i < tempLen; i++) { rxPacket.id |= idTempArray << ((tempLen - i - 1) * 8); } // RTR* * ptr = strToHex(ptr + 1, &rxPacket.rtr, &tempLen); // IDE* * ptr = strToHex(ptr + 1, &rxPacket.ide, &tempLen); // DATA* * ptr = strToHex(ptr + 1, rxPacket.dataArray, &rxPacket.dlc);_ #if RANDOM_CAN == 1* * // echo back* * printPacket(&rxPacket);* #else * sendPacketToCan(&rxPacket);* #endif } void RXcallback(void) { * static int rxPtr = 0;* * static char rxBuf[RXBUF_LEN]; _ while (Serial.available() > 0) {_ if (rxPtr >= RXBUF_LEN) { _ rxPtr = 0; } char c = Serial.read(); rxBuf[rxPtr++] = c; if (c == TERMINATOR) { rxParse(rxBuf, rxPtr); rxPtr = 0; } } } //------------------------------------------------------------------------------ // Setup* void setup() { * Serial.begin(250000); while (!Serial) { ; // wait for serial port to connect. Needed for native USB port only* * }_ #if RANDOM_CAN == 0* * randomSeed(12345);* * Serial.println("randomCAN Started");* #else * if (!CAN.begin(CAN_SPEED)) { _ Serial.println("Starting CAN failed!"); while (1); } // register the receive callback* * CAN.onReceive(onCANReceive); Serial.println("CAN RX TX Started"); #endif* } //------------------------------------------------------------------------------ // Main void loop() { * RXcallback();_ #if RANDOM_CAN == 1* * CANsimulate();* * delay(100);* #endif } My issue is I want to output the data stream to the serial Monitor but only show PID's that have changed. So the first ones show up and only ones that are different or have changed will show up in place of the old ones. So if the RPM Goes up the old state is replaced by the new state. I will then transport that data into a pyhton script to show the standing data. I just have no freaking clue how to filter it in serial monitor
As far as I understand PIDs and CAN IDs are not the same.
An ‘if(CAN.ID==whatImLookingFor)’
Is exactly what I’d do to isolate the possible data stream I wanted to watch/record.
Download a ‘DEV calculator’ I have a few on my phone, logic math, ASCII, HEX to Base10 to Binary etc, it’s essential when working with CAN imo. It is very handy to be familiar with bitwise operators too.
Sadly people are confused about High and Low Canbus terms too, while cars usually have a few separate CAN networks that run at different speeds, CAN high and CAN low refer to the wires/busses, they are mirrored, both going from around 2.6v, one up(high) and one towards 0, low. The system is quite tolerant and able to work in single wire mode during a partial fault, hence its use in vehicles.
I am interested in this topic but I have not actually done any of it myself yet. There is a guy converting his Rx8 to an EV and he is trying to recreate all the CAN messages that no longer exist. He has a lot of info on GitHub and YouTube including Arduino code linked through this post.
For example the CAN ID for combined throttle position, speed, and RPM sent to the instrument cluster on an Rx8 is 201. (if I am reading his spreadsheet correctly)
@Matty5 what does the "mydata" variable represent in your example? Is it a specifix hex value? How can I know what value to use for rpm or speed or whatever I need to read?
Also, why can't I ever get the id of value 12 to show up? I want to read the RPM, but that value never shows up...
@tobeymcmulled thank you for posting that code. What CAN board are you using? I tried using your link for the library but it said the page couldn't be found.
uint32_t whatIWant = 0x527; // ID that contains useful data.
uint8_t place = 5; //address in array
uint8_t mydata; //place to store value I want
void getdataifchange(){
If(CAN.ID==whatIWant){
If(CAN.data[place] != mydata){
mydata = CAN.data[place];
Serial.print(mydata); //prints in base10
}
}
}
in this example mydata is equalled to
ID [5]
0x526|8|10 1 0 60 7A 75 75 0
, on my vehicle that’s outside temp.
0x75 = 117
Temperature in C = (117/2)-50
To find these, it’s luck/digging for someone who already knows/ hard work and a process of elimination.
Do full reads at different times/conditions, compare all the data, what bytes change what bytes don’t? Sometimes data will be represented by weather a check sum is correct or not, for some things like on my car lights there is more or less a bit for each bulb hiding in there.
I believe the 12 you are seeking is an address that is spoken to via a specific diagnostics protocol not a CAN ID unless you’ve mixed up 12 and 0x12 in which case try looking for 18.
Please read the post at the start of any forum , entitled "How to use this Forum".
OR http://forum.arduino.cc/index.php/topic,148850.0.html.
Then look down to item #7 about how to post your code.
It will be formatted in a scrolling window that makes it easier to read.