The idea was to create a bluetooth device on the arduino to read data from the machine (ELM327 v2.1) using the HC-05 bluetooth module.

In this code the bluetooth module is switched to MASTER mode and connected to ELM327 using its MAC address. The responses of the module in the code are in the comments.

#include <SoftwareSerial.h>
SoftwareSerial bluetoothSerial(4, 5);

void setup() {
  getSetAtCommand("AT");                         //OK
  getSetAtCommand("AT+CMODE=0");                 //OK
  getSetAtCommand("AT+PSWD=1234");               //OK
  getSetAtCommand("AT+ROLE=1");                  //OK
  getSetAtCommand("AT+BIND=0010,CC,4F3603");     //OK
  getSetAtCommand("AT+LINK=0010,CC,4F3603");     //OK
  getSetAtCommand("AT+STATE?");  // OK: CONNECTED
  getSetAtCommand("ATZ");      //no answer, the buffer is empty                
  getSetAtCommand("AT015B");//no answer, the buffer is empty
  getSetAtCommand("015B");      //no answer, the buffer is empty          
  getSetAtCommand("ATRV");   //no answer, the buffer is empty
  getSetAtCommand("0105");    //no answer, the buffer is empty                  
  getSetAtCommand("at i");   //no answer, the buffer is empty
  getSetAtCommand("at rv");   //no answer, the buffer is empty                   
  getSetAtCommand("ati");    //no answer, the buffer is empty  
  getSetAtCommand("atrv");   //no answer, the buffer is empty

void loop() {

void getSetAtCommand(String command) {
  String added_command = command + "\r\n";
  while (bluetoothSerial.available()) {

Since the State returns CONNECTED, I assume that it is connected to the ELM.

To the command AT+PAIR=0010,CC,4F3603,20\r\n the module does not respond. The module also does not respond to commands such as ATZ\r\n, AT015B\r\n, 015B\r\n, ATRV\r\n, 0105\r\n, at i\r\n, at rv\r\n, ati\r\n, atrv\r\n. Sometimes the answer is ERROR[0], as I understand it means that there is no such command.

It seems to me that these commands don't reach the ELM at all. What can be the problem?

The ELM327 itself works, connects to the phone without any problems and shows the data.

Tried through the simulator on a laptop, the connected bluetooth model is displayed in the bluetooth devices on the laptop and also does not work as with the ELM.

  • Do you have a manual or doc for ELM327 v2.1 that you can share?
    – Fahad
    Commented Jun 16, 2023 at 9:42
  • @Fahad unfortunately not if you find on the Internet
    – gfd2
    Commented Jun 16, 2023 at 9:48
  • I can't seem to find it. How do you know of the AT commands? Is there an instruction somewhere?
    – Fahad
    Commented Jun 16, 2023 at 9:56
  • try AT$ ... it worked on old modems to display the available command set ... maybe the command is also implemented in your device
    – jsotola
    Commented Jun 16, 2023 at 16:00

1 Answer 1


ELM has closed down, but as a curtesy have kept their documentation site available. Here's the link to the last version published, V2.3 in 2020 -


The following is what I think the doc says -

  • ELM's external data exchange mechanism uses 4 bit hexadecimal 'nibbles', with 2 payload nibbles in each 8 bit 'byte' which may be 'padded' with a leading, empty, '0000' high nibble when only a single 4 bit low nibble is output.

  • ELM327 directives are text characters that start with 'AT' (or 'at', they are not case sensitive); these always have at least 1 bit set in their first high nibble. OBDII request nibbles start with a single hexidecimal 0 digit that never has any of the high nibble bits set, making it easy for the ELM327 to differentiate between directives and OBDII requests by examining just the first byte of any new message sent to it.

  • All exchanges are terminated with a Carriage Return , which is represented as ASCII code 13; 0xD, or C char '\r'. But Arduino (& C) Strings expect a 'nul terminator = '\0'. Take care to use commands that correctly terminate Strings.

  • A timeout period is used when awaiting a reply from the OBDII. After receipt a new timeout period is applied in case of additional responses (up to a '\r'). Only after a timeout does occur will the ELM327 pass the data out and prepare to become ready to accept a new directive or request. It will often do some internal housekeeping before sending the '\r' end of reply or the '>' ready prompt. [The timeout period is adaptive and will be automatically reduced to the shortest possible, safe time] [Sending a single nibble 'count' value at the end of a request returns as soon as it gets just that many data bytes from the OBDII]

  • The ELM327 uses '>' as a ready or prompt symbol. Do not attempt communication until a prompt has been received.

  • Incomplete or un-terminated commands will still be sent from the ELM327 to the OBDII after a timeout. The ELM327 will return a single '?' to indicate that the directive or request was either garbled, did not end in '\r' or no data was returned.

  • OBDII requests and data returns are defined as bytes. ELM327 exchanges are performed via nibbles. UARTs use char bytes. So OBDII requests must be converted from the byte form (shown in the docs) to padded nibbles to be sent/returned to/from the ELM327.

  • ELM327 Requests start with a 2 nibble Mode value from 0 1 to 0 B, then a 4 to 8 nibble Parameter IDentity (PID) and finally a 2 nibble representation of '\r' (0 D).

  • ELM327 Replies add 0x40 to the first Mode nibble, then pass out the modified Mode byte, the PID and returned data terminated with '\r'. This raw data stream needs to be re-assembled into separate bytes before interpreting.

  • The Mode & PID echo may be turned off, if desired, with the directive 'AT E0 \r'. Spaces are optional, 'ATE0\r' works fine. [Some older ELM327 devices may occasionaly return a '\0' = null charactor, these should be stripped from the raw data stream]

  • Sending a bare '\r' to the ELM327 is interpreted as 'repeat' the last directive or request. The fastest repeat data collecting method requires a request with a return data length nibble sent first, then a new '\r' as soon as the prompt appears.

OBDII data exchanges -

  • Headers, Mode, PID and checksums must be created/assembled by an external device then passed into an OBDII system using whichever protocol the vehical manufacturer has chosen (from the types defined in the OBDII specifications). Replies need to be verified (using returned Mode, PID and a return checksum); the ELM327 does all this pre- and post data encoding and verification internally. If you choose to do this in your own code then 'good luck'.

  • Returned data can be bit encoded, single bytes or a string of byte values. Some values are offset to extend the data range to include a negative portion within the bounds of a single, unsigned byte (0 to 255).

  • OBDII bytes are little-endian, and when combined into data words, whole data words are in little-endian order too.

  • The maximum returned 'standard' data payload is 41 bytes for various elapsed times, but most are much, much shorter.

  • Vehicle manufactures may return data of any desired length, so watch out for the '>' from the ELM327.

  • Data may be returned in multiple blocks. For example the 17 byte Vehical Identification Number (VIN) is returned as 5 off 4 byte words. Request Mode 09 PID 02 returns "0 1 2 3", "4 5 6 7", "8 9 10 11", "12 13 14 15", "16 x x x" with no '\r' between them, just the one at the end; this should be assembled into "0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 x x x".

Example ELM327 request - RPM is Mode 01, PID 12. Convert from decimal to hex - 01, 0C. Pad from 4 nibbles to 4 bytes - 00 01 00 0C. Add '\r'. Wait for '>', then send. Wait for reply to fully arrive ('\r' received).

Example ELM327 reply - 40 01 00 0C 1A F8 '\r'. Discard the modified request echo and header (if left On), then convert the returned hex to decimal 1A F8 -> 26 248, assemble the 2 byte word (26 * 256) + 248 = 6904. Divide /4 to convert to 1726 rpm. Wait till a '>' is received before sending a new request. Note little-endian byte order - 248 little end sent first, then big end (26 * 256).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.