ELM327 to RS232 in Linux
I have been having car problems recently. However, these inspired me to dig a little into the car ECU. ECUs (engine control units) are able to give out a myriad of information, like systems’ fault codes, real-time RPM, air-flow, fuel consumption, voltage, exhaust state, and lots more. Most modern cars tap into this information and display it on the dashboard via on-board computers.
I finally received the long-awaited OBD II (OBD 2) (on-board diagnostics) interpreter based on the ELM327 chip yesterday and all I can say is that it’s been a long night.
Update September 9th, 2014: turns out, unsurprisingly, that the board I had was a fake one, containing no ELM327 chip inside, but rather PIC microcontroller that emulated the expensive proprietary genuine ELM327 chip. This is probably why I never managed to get it to work on my car.
The ELM327 interpreter chip provides an RS-232 (serial) interface to ODBII data. The device that I got further provides a UART interface via its FT232R FTDI chip which provides a USB interface. So, in short, the package provides a USB interface on one end, and an OBDII interface on the other.
Ubuntu comes with FTDI support out of the box, and the ELM327 device was detected without problems.
usb 5-1: new full speed USB device using uhci_hcd and address 2 usb 5-1: configuration #1 chosen from 1 choice usbcore: registered new interface driver usbserial USB Serial support registered for generic usbcore: registered new interface driver usbserial_generic usbserial: USB Serial Driver core USB Serial support registered for FTDI USB Serial Device ftdi_sio 5-1:1.0: FTDI USB Serial Device converter detected usb 5-1: Detected FT232RL usb 5-1: Number of endpoints 2 usb 5-1: Endpoint 1 MaxPacketSize 64 usb 5-1: Endpoint 2 MaxPacketSize 64 usb 5-1: Setting MaxPacketSize 64 usb 5-1: FTDI USB Serial Device converter now attached to ttyUSB0 usbcore: registered new interface driver ftdi_sio ftdi_sio: v1.5.0:USB FTDI Serial Converters Driver Bus 005 Device 003: ID 0403:6001 Future Technology Devices International, Ltd FT232 USB-Serial (UART) IC
The device becomes available on a ttyUSB#
terminal and can be connected to and read from. There aren’t many tools out there for Linux, and although Wine runs most Windows OBDII software they don’t give any insight into how to do things work down the wire (unless you hook up a serial sniffer).
The screen
manager, terminal emulator, allows one to connect to the device at a specific baud setting. The hardest part is usually getting the bauds right, otherwise the chip will not respond or will respond with gibberish. The ELM327 datasheet says that in a normal state the baud rate is usually set to 38400 Bd (9600 Bd in sleep mode). The setting is reset when the device is left without power.
Speaking of power, some devices might not power the ELM327 chip via USB and have to be connected to the OBDII port of the car in order to respond. My particular device package work without being connected to an OBDII port.
And by the way, I am not to be held responsible for whatever happens to your device or your car, proceed at your own risk.
screen /dev/ttyUSB# 38400
should work (replace the # with the registered number of the device). ELM327 is an ASCII interpreter, so it can just be typed to. All commands start with “AT” (standing for ATtention and end with a carriage return (0x0d
, Ctrl+V Ctrl+M, sent automatically by the screen
terminal). By default echoing is ON
, line feeds are OFF
. The short AT commands list is of much help.
# these are comments for clarification, do not type in terminal AT L1 # enable line feed OK > AT I # information ELM327 v1.4 >AT @1 # device description OBDII to RS232 Interpreter > AT DP # current protocol AUTO > AT RV # car is obviously not connected, no voltage on OBDII port 0.0V
One wouldn’t want to remember the ELM327 command set, to say nothing of the ISO 15765-4 CAN, SAE J1850 PWM, SAE J1850 VPW, ISO 9141-2, ISO 14230-4 and SAE J1939 protocols that it speaks. You don’t see car diagnosticians pounding away inside a terminal, cross-referencing hex responses with car manufacturer data sheets, regardless of how #geek that would be.
There are a lot of so called scanning tools, diagnostic software packages for Windows. But only a couple that should work under Linux.
- Ross Tech’s VAG COM, VCDS, both appear to be free and work well under Wine. Don’t forget to
ln -sf /dev/ttyUSB# ~/.wine/dosdevices/com1
and select COM port 1. - pyOBD, free, open-source and cross-platform, uses pySerial. Does not work with ttyUSB# out of the box, requires very minor bugfixing. If interested let me know in the comments.
- freediag – Vehicle Diagnostics Suite, open-source, coded in C. Couldn’t compile version 1.0c, other versions refused to work.
Rolling your own
I did want to play around with it from code, so the ELM327 would actually respond to automated requests, etc. I chose good old C for experiments over Python’s pySerial, or PHP’s php-serial, or even Haskell’s serialport. Linux has a serial C library declared in termios.h
which should work, however I chose to try out ftdi.h
(although scarcely documented, some examples are available), which is part of the libftdi-dev
package.
#include <stdio.h> #include <ftdi.h> #include <string.h> int main(int argc, char * argv[]) { struct ftdi_context ftdic; /* FTDI context */ /* line properties */ enum ftdi_bits_type bits = BITS_8; // 8 data bits enum ftdi_stopbits_type sbit = STOP_BIT_1; // 1 stop bit enum ftdi_parity_type parity = NONE; // no parity enum ftdi_break_type lineend = BREAK_OFF; int ret; unsigned short modem_status = 0; unsigned char * tx; unsigned char * rx; int i,j; /* general purpose indices */ if ((tx = malloc(128)) == NULL || (rx = malloc(128)) == NULL) { fprintf(stderr, "Error: could not allocate memory\n"); return EXIT_FAILURE; } if (ftdi_init(&ftdic) < 0) { fprintf(stderr, "Error: could not initialize FTDI\n"); return EXIT_FAILURE; } if ((ret = ftdi_usb_open(&ftdic, 0x0403, 0x6001)) < 0) { fprintf(stderr, "unable to open ftdi device: %d (%s)\n", ret, ftdi_get_error_string(&ftdic)); return EXIT_FAILURE; } if ((ret = ftdi_usb_reset(&ftdic)) < 0) { fprintf(stderr, "unable to reset ftdi device: %d (%s)\n", ret, ftdi_get_error_string(&ftdic)); return EXIT_FAILURE; } if ((ret = ftdi_usb_purge_buffers(&ftdic)) < 0) { fprintf(stderr, "unable to purge buffers on ftdi device: %d (%s)\n", ret, ftdi_get_error_string(&ftdic)); return EXIT_FAILURE; } if ((ret = ftdi_set_baudrate(&ftdic, 38400)) < 0) { fprintf(stderr, "unable to set ftdi device baud rate: %d (%s)\n", ret, ftdi_get_error_string(&ftdic)); return EXIT_FAILURE; } if ((ret = ftdi_set_line_property2(&ftdic, bits, sbit, parity, lineend)) < 0) { fprintf(stderr, "unable to set ftdi device properties: %d (%s)\n", ret, ftdi_get_error_string(&ftdic)); return EXIT_FAILURE; } ftdi_poll_modem_status(&ftdic, &modem_status); printf("R232 status: 0x%x\n", modem_status); memcpy(tx, "AT E0\r", 6); /* turn echoing off */ if (ftdi_write_data(&ftdic, tx, sizeof(unsigned char) * 6) < 0) { fprintf(stderr, "unable to send ftdi device data: %d (%s)\n", ret, ftdi_get_error_string(&ftdic)); return EXIT_FAILURE; } while (1) { j = 0; while ( ( ret = getchar() ) != 0x0a && j < 128) tx[j++] = ret; if (!j) continue; tx[j++] = 0x0d; /* end */ ret = 0; ftdi_usb_purge_tx_buffer(&ftdic); ftdi_usb_purge_rx_buffer(&ftdic); for (i = 0; i < j; i++) { ret += ftdi_write_data(&ftdic, tx+i, sizeof(unsigned char) * 1); } printf("Written %d bytes of data: ", ret); for (i = 0; i < ret; i++) printf("0x%x ", tx[i]); printf("\n"); while (1) { ret = ftdi_read_data(&ftdic, rx, 128); if (ret > 0) { printf("Read %d bytes of data\n", ret); ret -= 3; /* remove > prompt */ printf("\tDATA: "); for (i = 0; i < ret; i++) printf("0x%x ",rx[i]); printf("\n\t("); for (i = 0; i < ret; i++) printf("%c",rx[i]); printf(")\n"); break; } else if (ret < 0) { fprintf(stderr, "unable to read from ftdi device: %d (%s)\n", ret, ftdi_get_error_string(&ftdic)); return EXIT_FAILURE; } } } if ((ret = ftdi_usb_close(&ftdic)) < 0) { fprintf(stderr, "unable to close ftdi device: %d (%s)\n", ret, ftdi_get_error_string(&ftdic)); return EXIT_FAILURE; } ftdi_deinit(&ftdic); free(tx); free(rx); return EXIT_SUCCESS; }
This code is very primitive and you’ll find it horrible (especially that buffer overflow ;)), but it works. Compile by linking to libftdi
. Note the libftdi
requires root permissions to run, which is one of its disadvantages.
~/tmp/obd$ make gcc -Wall -ggdb -lftdi obdtest.c -o odb ~/tmp/obd$ sudo ./odb R232 status: 0x6001 AT I Written 5 bytes of data: 0x41 0x54 0x20 0x49 0xd Read 14 bytes of data DATA: 0x45 0x4c 0x4d 0x33 0x32 0x37 0x20 0x76 0x31 0x2e 0x34 (ELM327 v1.4) AT @1 Written 6 bytes of data: 0x41 0x54 0x20 0x40 0x31 0xd Read 29 bytes of data DATA: 0x4f 0x42 0x44 0x49 0x49 0x20 0x74 0x6f 0x20 0x52 0x53 0x32 0x33 0x32 0x20 0x49 0x6e 0x74 0x65 0x72 0x70 0x72 0x65 0x74 0x65 0x72 (OBDII to RS232 Interpreter)
This will probably end up as either Python code or C++ code, haven’t decided yet which route to take. I’ll be further experimenting in C for now, maybe with termios.h
instead of libftdi
to see whether it works just as well without requiring root permissions. This is just the beginning; parsing actual ODB II data will be where the real fun begins. I’ll be aiming for a command line interface first and see where I can take it from there.
Thank You very much for this post!!! Baud 38400 works fine with “screen” command! I face some problem with pyobd – after connection attempt “stty -F /dev/ttyUSB0” shows 9600, not 38400 as set previously. What is this “minor bugfixing”? π
Paul, thanks for dropping by. In my case, the
pyobd
Configuration screen does not displayttyUSBx
devices due to wrong port-scanning formats. If you openpyobd
around line 623, in thescanSerial
routine and finds = serial.Serial("/dev/ttyUSB"+str(i))
then line belowavailable.append( (i, s.portstr))
it adds a tuple instead of just the port string like in other cases, so the solution is toavailable.append(s.portstr)
instead. Hope this helps.Thank You for answering! In my Configuration screen no options for choosing serial ports are available – wxPython-2.9.3.1 was the only version of wxPython I compiled on slackware-13.37, sorry. The port is chosen via self.COMPORT=”/dev/ttyUSB0″ statement, line 359. I hope this way of port setting is Ok for my own notebook π My problem with pyobd is probably baud changing during connection attempt – I’ve got 38400 shown by βstty -F /dev/ttyUSB0β³ before the attempt and 9600 after the attempt. I’ll try to change defaults in serialutil.py or elsewhere in my pyserial-2.6 and report later – my ELM327 chip is not powered via USB (connection to an OBDII port is needed).
Nice! I’m curious how you got pyOBD working. Which version worked? I tried a modified version at github, but even with modifications a few minor bugs, it still doesn’t work for me.
https://github.com/peterh/pyobd/issues/1#issuecomment-6167763
Luke, a comment above explains the modification I had to do https://codeseekah.com/2012/02/22/elm327-to-rs232-in-linux/#comment-1816 to get pyODB working. I used version 0.9.2-2 (2010-06-20). What doesn’t work for you? What kind of errors do you get? Have you tried this fork: https://github.com/rememberthe8bit/pyobd it appears to have removed the bug I talk about.
Thank for contributing fixes to that repository. Unfortunately it still shows the same error when I connect to the car computer, in my terminal it shows “boguscode?0100”.
I had to modify the code to even get it to report these errors, as it seems exceptions are handled differently in newer Python:
https://github.com/programmin1/pyobd/commit/727307787bc9283ef6858e412f3361228acad49e
What version of Python are you trying to run this on? I think I ran it on 2.6. The good news is that you do have the connection, so pyOBD is at least communicating. Bogus code might be raised by invalid bytes passed on caused by a non-matching baud rate. You can try tracing what bytes are sent over the wire and then replay them using a terminal
screen /dev/ttyUSB# 38400
as described in my post above, then see what is actually being returned by the interface, compare to specs. Keep me posted, please!On latest Ubuntu 12.04, I have Python 2.7.3 and Python-serial 2.5-2.1build1. How would I trace the bytes over usb? I think I tried using Wireshark but it didn’t work.
I couldn’t enter AT commands in screen, at least, it showed no response and simply went back to the beginning, typing again overwrote what I had typed. Maybe we are using different versions?
Tapping into what’s being sent is as simple as adding some print statements to pyOBD, the OBD protocol is all ASCII so there shouldn’t be any malformed characters. Other methods include the
usbmon
utility, which should be build-in.You’ll need root; wireshark should work with it too. Check this out: https://biot.com/blog/usb-sniffing-on-linux. This may not work, since RS232 is talked to in a completely different protocol and you might not see the actual serial ASCII bytes in their final form. Back in the day when I wrote this article I used something else, a serial sniffing tool of some sorts, I don’t remember which one exactly, but it created a new device which would pipe everything to the real device and you would connect to the new device and screen into it or something. Sort of https://www.cyberciti.biz/faq/howto-monitor-data-on-a-serial-port-in-linux/, if I remember I’ll let you know exactly.
The fact that
screen
ing into the device didn’t work raises some concerns as to the state of the device itself, could it be broken? Have you tried different baud rates? Try sending a ^M character at the end (Ctrl+V Enter). Do any of the LEDs on the device light up in any way when you type and hit return?Try putting together a quick Python script using the serial libraries and send in some AT command like I did in C above.
I’m using this device, which only has one LED, that lights up when plugged in to vehicle. It must not be compatible?
https://www.amazon.com/OBD2-Diagnostic-Cable-KKL409-1-VAG-COM/dp/B002WIN8VQ/
Product description even says, “under special circumstances this can give some problems regarding time especially on the handshake lines.”
Have you tried different baud rates, bit parity settings, etc. when using screen? AT commands have to work for sure.
Hello!
I have a bluetooth elm327 shopped at a Chinese website.
After plug in and configure it, i was trying to communicate with the screen using the command as stated in the article with good results.
It was impossible to use with the pyodb. I suspect that due to the hardware version (1.5a) the device returns unexpected things with what pyodb dizzy.
Did anyone have a similar problem?
with FiatEcuScan with wine that works fine.
i tried using the unix ‘tee’ command to log the serial port communication but did not know how. π I think it is due to the bidirectionality of communication.
Any help is welcome, congratulations for your blog.
Gabriel, thanks for stopping by. Have you tried out this fork of pyODB? Did it connect? Did the baud rate get set correctly? These Chinese knockoff ELM327 are quite unstable at times and may return garbage, but from what it sounds you’re communicating with pyobd at a different baud rate. I got gibberish too until I found the correct baud rate.
Thank you for your quick response.
I tried different connection speeds (changing it in the source code pyodb) but without success, I will continue investigating. I will report if I have success
Any idea to log the serial port communication?
Gabriel
Gabriel, there appear to quite a bit of tools for serial and USB sniffing, including Wireshark. Check out sniff serial port on Linux and other similar questions around. I don’t remember which tool I used, but it created a man-in-the-middle tty.
thanks, I’ll keep you informed
Hello, I bought a ELM 327 USB when I connected it to the car wI received a message that the interface is not connected with the ECU. I’ve tried it with 2 different scan program and it gives me the same result. What is the problem and if there is someone who can help me out.
Pierre, it is very hard to guess what may be wrong. The adapter itself, the ECU, the connections, the software. Have you tried low-level
ttyUSB
interfacing for testing instead of any software. Just talk to the device directly to see if it responds viascreen
or I thinkPuTTY
does it too if you have no access toscreen
. Taking the low-level approach will help you diagnose the issues you’re facing. Hope this helps.Hi,
I recently bought an ELM237 to RS232 cable. Before I was using a PEAK CAN-USB converter and pySerial to record CAN streams.
What I want to get is a constant stream of CAN bus. But after connecting the cable, powering the car, and opening mini com with correct baud rates and all, I don’t get a stream. If I send AT commands it answers, but I can’t manage to get a stream. When I do AT MA it tells me BUFFER FULL. This might be the reason but still I should at least see some lines before the buffer gets full.
Any ideas? Am I expecting something that ELM237 doesn’t suppose to do?
Thanks
Akin, you can look at the full list of supported commands and error messages here https://elmelectronics.com/DSheets/ELM327DS.pdf the
BUFFER FULL
message is also described there. The ELM327 does support monitoring all (MA
) mode and streaming (“Monitoring The Bus” section in the datasheet and “CAN Messages and Filtering” after it, and “MA” in “AT Command Descriptions” gives a good overview of the command). You can, by the way, issue anAT BD
command to dump the current buffer. Hope this helps, and let me know how it goes. Good luck!Thanks for your quick answer. Yes I had checked the manual for the commands and the error.
I use minicom to communicate with the device. I send the command “AT MA” first hoping that it will return me a continuous stream of data. But it tells me Buffer full. Then if I do “AT BD” to flush the buffer nothing happens.
I will try to send the commands first with mini com, then read the bus with a python script. All the docs and examples that I saw in internet are using ELM237 with request-answer based approaches thought. I seriously doubt that this device supports constant streaming. I will make additional tests…
Thanks for the helpful article Soulseekah, I had a similar problem and wanted to do the same thing, I just didn’t have enough knowledge and the know how. Keep up the good work man π
I installed pyobd successfully and followed the steps presented on :
https://www.totalcardiagnostics.com/support/Knowledgebase/Article/View/19/0/how-to-install-elm327-usbbluetooth-on-mac-and-obd-software
to install the drivers. However, as I run :
pythonw2.7 pyobd
and then select OBDII/Configure, I get :
Traceback (most recent call last):
File “pyobd”, line 659, in Configure
ports = self.scanSerial()
File “pyobd”, line 609, in scanSerial
s = serial.Serial(i)
File “/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/serial/serialutil.py”, line 282, in __init__
self.open()
File “/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/serial/serialposix.py”, line 289, in open
self.fd = os.open(self.portstr, os.O_RDWR|os.O_NOCTTY|os.O_NONBLOCK)
OSError: [Errno 2] No such file or directory: ‘/dev/cuad0’
in the prompt…
How should I solve this? In dev I have :
/dev/cu.usbserial
/dev/tty.usbserial
How should I proceed to make it work?
Kind Regards,
Tonkiplis
I did patch my version of pyOBD, since it couldn’t detect the names correctly. I forgot where exactly, but if you grep through the files and look where the names are coming from you can actually just add your device directly. This worked fine for me. Let me know how it goes.
I fixed it by hardcoding the
self.portstr
higher up in the source code to the actual device it gets allocated to. Does this work for you?I forgot to mention this, but I am working on mac os x 10.6.8
I use python 2.7
and I tried 2 versions (git) of the pyobd with same results.
Hi,
I have bought the ELM327 I want to make a interface with raspebrry with c++. Any ideas?
Godspeed π Don’t forget to open-source the library/utility for others to enjoy. To start you’d look at the protocol specifications. There are a lot of ODB projects out right now, just do a Google search.
Hi.
I have a USB OBD cable but I can’t get anything from it and can’t find what is wrong.
When I use screen and type “screen /dev/ttyUSB0” it switches me to “[screen0:ttyUSB0]” but I don’t get anything !
I don’t even get the ELM327’s prompt “>” and can’t type anything !
Any idea I could go further.
I’ve also have a Bluetooth OBD device and can’t get anything from it, but as there is the bluetooth device on top, I think it’s better try simple things first.
I’ve been trough screen instead of using a sofware because I’m with Linux and the only software available is PyOBD witch seems to be buggy !
Any help ?
Incorrect baudrates or bit parity is usually manifested as no response or garbage data. Does the adapter at least blink when you try typing something? Have you looked at the system logs? I’m on Linux as well and the adapter worked fine for me. What you can try to do is get access to a Windows machine and see if it works there, to rule out the possibility of a faulty adapter.
You write: “the board I had was a fake one, containing no ELM327 chip inside, but rather PIC microcontroller that emulated the expensive proprietary genuine ELM327 chip”
Note that the original ELM327 is exactly that: A PIC Β΅C+firmware on it. It never was a custom chip, so not necessarily a fake if you find a PIC inside π
Ah, I see, thanks for letting me know. But the price of a licensed chip is almost 3 times higher than what I paid for the whole thing so it’s definitely an “unlicensed” version of the firmware.
Hi, I’ve tried your program and when I send the AT commands I get the same hex back, almost like it just sends the same data back.
I tried the same commands E.G. AT @1 and just get “Written 6 bytes of data: 0x41 0x54 0x20 0x40 0x31 0xd
Read 6 bytes of data
DATA: 0x41 0x54 0x20
(AT )
”
Any idea why?
God knows. Could be a faulty adapter, a faulty cable, or something else hardware-related. If you figure it out let me know here.
[…] to read data from an ECU through OBD2 to USB cable in C or C++. I have tried running this example (https://codeseekah.com/2012/02/22/elm327-to-rs232-in-linux/) but for some reason it doesn’t return the same […]