OBD II Bike Connector - Pass via bluetooth

I've just take some measurements with my kawa er6 '09.

From the last byte of the request, ECU responds in 29ms.
The whole response takes 7.2ms for a gear request (8bytes).

Hope this info is helpful to squeeze the speed! :cowboy_hat_face:

The most interesting duration would be the delay between two responses. My ECU (Kawasaki Z750r 2012) idles for about 100ms, before responding to the next request. That´s where the "high" timings come from.
But I´m willing to write a test to measure it again, more precisely!

I made the measurements with this cheap tool:

Maybe it could be helpful for you.

Since I´ve added a SD logger, I already have all the timings, avalilable.
But still with the 5ms delay between the bytes and 55ms between two messages (which is automatically exeeded by the ECU delay).
However, I´ll give it a try! It´s better to have than to be in need :smiley:

My logs look like that:

Logger activated
>81 11 F1 81 04;16848
<80 F1 11 03 C1 EA 8F BF;16915
>80 11 F1 02 10 80 14;16916
<80 F1 11 02 50 80 54;17038
>80 11 F1 01 3E C1;17039
<80 F1 11 01 7E 01;17152
>80 11 F1 02 21 00 A5;17153
<80 F1 11 06 61 00 DF F7 87 87 CD;17277
>80 11 F1 02 21 01 A6;17295
<80 F1 11 03 61 01 05 EC;17399
>80 11 F1 02 21 02 A7;17400
<80 F1 11 03 61 02 00 E8;17519
>80 11 F1 02 21 04 A9;17520
<80 F1 11 04 61 04 00 C9 B4;17640
>80 11 F1 02 21 05 AA;17642
<80 F1 11 04 61 05 CE 00 BA;17763
>80 11 F1 02 21 06 AB;17780
<80 F1 11 03 61 06 6A 56;17889
>80 11 F1 02 21 07 AC;17891
<80 F1 11 03 61 07 3F 2C;18010
>80 11 F1 02 21 08 AD;18011
<80 F1 11 04 61 08 CD 80 3C;18130
>80 11 F1 02 21 09 AE;18131
<80 F1 11 04 61 09 00 00 F0;18250
>80 11 F1 02 21 0A AF;18251
<80 F1 11 03 61 0A 9F 8F;18369
>80 11 F1 02 21 0B B0;18371
<80 F1 11 03 61 0B 00 F1;18490
>80 11 F1 02 21 0C B1;18491
<80 F1 11 04 61 0C 00 00 F3;18611
>80 11 F1 02 21 0E B3;18612

The numbers are plain millis() from the logger. So it´s a timestamp after fully receiving < a message and before sending > (for time optimization purpose, the 55ms delay happes afterwards, to potentially subtract the log-duration from it).
To get a valid result, you need to subtract the time between two received messages, which gives me an average of 120ms.

Thanks to everyone, I was able to communicate with the my motorcycle.
We have made some changes and hope that they will be useful.
My bike is Kawasaki Ninja250r '08-'12 (EX250K) .
ECU_Reader.ino 24 const uint8_t MyAddr = 0xF1; → 0xF2;
However,although I was able to communicate, I underestimated the voltage and burned the Arduino Uno and HC-06 :pensive:
So, I will port it to M5STICK-C and proceed with the project.
It is a secret to my wife to order Arduino Uno and HC-06 again. :rofl: :rofl:

Nice to hear, that you made it working!
At least for a period of time :wink:

That´s interesting with 0xF2, because this is the Tester-Adress (sender), which could/should be anything!
The important part is the receiver, which is 0x11 on my ECU and 0x28 on the ABS.
I´ll take a look if I can find you bike on my diagnostic device and see what it will send out.

Hello, TriB
Thank you for your reply
Certainly, I was surprised that the reply came with the tester address 0xF2 instead of the ECU address.
I tried it for two reasons

  1. Kawaduino (by Tomnz)
  2. I checked the waveform of the shift indicator.
    It was 0xF2 above, so I tried it.
    Throttle, Temperature, Pressure and Voltage communication OK,
    No response revs.
    After that, smoke came out ... :scream: :scream: :scream:

Hi everyone!

I'm starting to play a little bit with my bike (z750 from 2011) and I was hoping to develop a gear indicator as a project. I found the incredile work of yours and, following your steps, I tried to use the L9637D to connect to the bike's ECU. Unfortunately, I couldn't connect with the bike.

I'm using an arduino nano with the fastInit function developed by Tom Mitchell (only that part of the code, just to test). I suppose the problem is with the circuit of the L9637D. I followed Tom's schematic and tried to replicate your schematic TriB, but i can get it working.

It would be useful if u could provide some more information about this connection. Maybe I'm doing something wrong (I'm noob in this kind of projects) and I want to know if it is my fault or maybe the chip is broken.

Thanks a lot in advance :slight_smile: Best regards,


Hello Guillem,

how can we help you, without seeing your schematic or knowing what exactly does not work?
The L9637D is a great startingpoint, which only needs a Pullup resistor (510 Ohm) between VIN and K-Line.
And a 100n capacitor between K-Line and GND.

An important thing is, to connect Rx to Rx and Tx to Tx, instead of twisting them.

Fast Init will not give you any response, you need to (at least) send out the start communication request, to receive something.

Hi TriB,

First of all thanks for your rapid response. Regarding the L9637D, I have used the 510 Ohm pull-up resistor and the 100 nF capacitor as indicated. I have the following prototype:

The yellow cable connected to the capacitor and pin 6 corresponds to the K-line, and the black one to the Vin which I connect to the 12V supply from the bike and pin 7. I also connect ground to the bike, and the 5V from arduino to pin 3 of L9637D.

The software updated to the nano is the one developed by Tom Mitchell, which I have slightly modified to only check if I am able to connect to the bike (just start communication request).
(it seems i'm not allowed to upload files since I'm a new user)


class Color {
  Color() {};
  Color(int r, int g, int b) : r(r), g(g), b(b) {};
  int r, g, b;
  uint32_t toUint32() { return ((uint32_t)r << 16) | ((uint32_t)g <<  8) | b; };


#include <EEPROM.h>
#include "kawaduino.h"

#define K_OUT 1 // K Output Line - TX on Arduino
#define K_IN 0 // K Input Line - RX on Arduino
#define SERIAL_ON 3

// Animation settings
#define REFRESH_MICROS 30000
// Mode 1
#define MAX_RPM 7000
#define MIN_RPM 1200
#define MIN_COL 180
#define MAX_COL 255
#define MIN_BRIGHT 25
#define MAX_BRIGHT 255
// Mode 2
#define MAX_MPH2 160
#define MPH_DOTS 3
#define MPH_DOT_SIZE 3
#define MIN_MPH2 4
#define BACKGROUND_MAX 0.4

// LED settings
#define N_PIXELS 60
#define LED_PIN 6
#define DIAG_LED1 4
#define DIAG_LED2 5
#define BOARD_LED 13

// Modes
#define N_MODES 2
#define MODE_ADDR 0
#define BTN_PIN 7

// Startup
#define AVG_CYCLES 50

// Timings
#define MAXSENDTIME 2000 // 2 second timeout on KDS comms.
const uint32_t ISORequestByteDelay = 10;
const uint32_t ISORequestDelay = 40; // Time between requests.

// Addresses
const uint8_t ECUaddr = 0x11;
const uint8_t myAddr = 0xF2;

const uint8_t validRegs[] = { 0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08,
  0x09, 0x0A, 0x0B, 0x0C, 0x20, 0x27, 0x28, 0x29, 0x2A, 0x2E, 0x31, 0x32,
  0x33, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x44, 0x54, 0x56, 0x5B, 0x5C, 0x5D,
  0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x6E,
  0x6F, 0x80, 0x9B, 0xA0, 0xB4 };

const uint8_t numValidRegs = (uint8_t)(sizeof(validRegs));

//  strip = Adafruit_NeoPixel(N_PIXELS, LED_PIN, NEO_GRB + NEO_KHZ800);

bool ECUconnected = false;

// Animation variables
unsigned long lastFrameTime = 0;
// Mode 1
uint32_t rpms = 0;
uint32_t dampedRpms = 0;
// Mode 2
uint32_t mph2 = 0;
float dampedMph2 = 0;
float backgroundLevel = 0;
float progress = 0;

// Modes
uint8_t mode = 0;
boolean btnPressed = false;

void setup() {
  // Setup pins
  pinMode(K_OUT, OUTPUT);
  pinMode(K_IN, INPUT);
#ifdef SERIAL_ON
#ifdef DIAG_LED1
  pinMode(DIAG_LED1, OUTPUT);
#ifdef DIAG_LED2
  pinMode(DIAG_LED2, OUTPUT);
  digitalWrite(BOARD_LED, LOW);

  // Show startup routine
//  strip.begin();

//  startupLeds();
  // Read mode
  mode = EEPROM.read(MODE_ADDR);
  lastFrameTime = micros();
  // Determine duration of updateLeds()
//  determineAverage();

void loop() {
  // Init blink
#ifdef SERIAL_ON
  digitalWrite(SERIAL_ON, HIGH);
  digitalWrite(BOARD_LED, HIGH);
  digitalWrite(BOARD_LED, LOW);
  digitalWrite(BOARD_LED, HIGH);
  digitalWrite(BOARD_LED, LOW);
  uint8_t cmdSize;
  uint8_t cmdBuf[6];
  uint8_t respSize;
  uint8_t respBuf[12];
  uint8_t ect;

  if (!ECUconnected) {
    // Start KDS comms
    ECUconnected = initPulse();

    if (ECUconnected) {
      // Show we're connected
      digitalWrite(BOARD_LED, HIGH);
    } else {

// Initialize connection to ECU
bool initPulse() {
  uint8_t rLen;
  uint8_t req[2];
  uint8_t resp[3];

  // This is the ISO 14230-2 "Fast Init" sequence.
  digitalWrite(K_OUT, HIGH);
  digitalWrite(K_OUT, LOW);
  digitalWrite(K_OUT, HIGH);


  // Start Communication is a single byte "0x81" packet.
  req[0] = 0x81;
  rLen = sendRequest(req, resp, 1, 3);

  // Response should be 3 bytes: 0xC1 0xEA 0x8F
  if ((rLen == 3) && (resp[0] == 0xC1) && (resp[1] == 0xEA) && (resp[2] == 0x8F)) {
    // Success, so send the Start Diag frame
    // 2 bytes: 0x10 0x80
    req[0] = 0x10;
    req[1] = 0x80;
    rLen = sendRequest(req, resp, 2, 3);
    // OK Response should be 2 bytes: 0x50 0x80
    if ((rLen == 2) && (resp[0] == 0x50) && (resp[1] == 0x80)) {
      return true;
  // Otherwise, we failed to init.
  return false;

// Send a request to the ECU and wait for the response
// request = buffer to send
// response = buffer to hold the response
// reqLen = length of request
// maxLen = maximum size of response buffer
// Returns: number of bytes of response returned.
uint8_t sendRequest(const uint8_t *request, uint8_t *response, uint8_t reqLen, uint8_t maxLen) {
  uint8_t buf[16], rbuf[16];
  uint8_t bytesToSend;
  uint8_t bytesSent = 0;
  uint8_t bytesToRcv = 0;
  uint8_t bytesRcvd = 0;
  uint8_t rCnt = 0;
  uint8_t c, z;
  bool forMe = false;
  char radioBuf[32];
  uint32_t startTime;
  for (uint8_t i = 0; i < 16; i++) {
    buf[i] = 0;
  // Zero the response buffer up to maxLen
  for (uint8_t i = 0; i < maxLen; i++) {
    response[i] = 0;

  // Form the request:
  if (reqLen == 1) {
    buf[0] = 0x81;
  } else {
    buf[0] = 0x80;
  buf[1] = ECUaddr;
  buf[2] = myAddr;

  if (reqLen == 1) {
    buf[3] = request[0];
    buf[4] = calcChecksum(buf, 4);
    bytesToSend = 5;
  } else {
    buf[3] = reqLen;
    for (z = 0; z < reqLen; z++) {
      buf[4 + z] = request[z];
    buf[4 + z] = calcChecksum(buf, 4 + z);
    bytesToSend = 5 + z;
  // Now send the command...
  for (uint8_t i = 0; i < bytesToSend; i++) {
    bytesSent += Serial.write(buf[i]);
  // Wait required time for response.
//  delayLeds(ISORequestDelay, false);
  startTime = millis();
  // Wait for and deal with the reply
  while ((bytesRcvd <= maxLen) && ((millis() - startTime) < MAXSENDTIME)) {
    if (Serial.available()) {
      c = Serial.read();
      startTime = millis(); // reset the timer on each byte received

//      delayLeds(ISORequestByteDelay, true);

      rbuf[rCnt] = c;
      switch (rCnt) {
      case 0:
        // should be an addr packet either 0x80 or 0x81
        if (c == 0x81) {
          bytesToRcv = 1;
        } else if (c == 0x80) {
          bytesToRcv = 0;
      case 1:
        // should be the target address
        if (c == myAddr) {
          forMe = true;
      case 2:
        // should be the sender address
        if (c == ECUaddr) {
          forMe = true;
        } else if (c == myAddr) {
          forMe = false; // ignore the packet if it came from us!
      case 3:
        // should be the number of bytes, or the response if its a single byte packet.
        if (bytesToRcv == 1) {
          if (forMe) {
            response[0] = c; // single byte response so store it.
        } else {
          bytesToRcv = c; // number of bytes of data in the packet.
        if (bytesToRcv == bytesRcvd) {
          // must be at the checksum...
          if (forMe) {
            // Only check the checksum if it was for us - don't care otherwise!
            if (calcChecksum(rbuf, rCnt) == rbuf[rCnt]) {
              // Checksum OK.
            } else {
              // Checksum Error.
          // Reset the counters
          rCnt = 0;
          bytesRcvd = 0;
          // ISO 14230 specifies a delay between ECU responses.
//          delayLeds(ISORequestDelay, true);
        } else {
          // must be data, so put it in the response buffer
          // rCnt must be >= 4 to be here.
          if (forMe) {
            response[bytesRcvd] = c;

  return false;

// Checksum is simply the sum of all data bytes modulo 0xFF
// (same as being truncated to one byte)
uint8_t calcChecksum(uint8_t *data, uint8_t len) {
  uint8_t crc = 0;

  for (uint8_t i = 0; i < len; i++) {
    crc = crc + data[i];
  return crc;

As far as I can tell from here, it looks okay.
But I cannot see if Rx goes into Rx or Tx.

Do you connect you PC while trying to communicate with the bike? I guess, by missing voltage divider for powering the nano.
The Rx & Tx (Pin 0 & 1) are the same as your USB uses. It won´t work if you have them connected!

Do you have a USB-TTL or something? You currently are going totally blind. Using a SoftwareSerial to submit data to the PC would be good to see if there is any response. Or take another Nano, an SD Card or something to see/write whats going on there, without intefering the Hardware Serial.

Another thing would be to use my ECU Emulator on your PC to see if the values are fine. But I think they are, from the code above. The problem will be the blocking Serial by USB.

Thanks a lot! Rx is connected to Rx and Tx to Tx. I suppose it might be what you suggest, that I power the arduino from the PC and that interfers the Hardware Serial. I will try to power the arduino with a 12v to 5v DC converter that I have, but in this case I will include the capacitor on the VCC port to stabilise the voltage value (I think the converter is not really good).

Again, thank you for your time. I hope this solves the problem!

Hi again!

Unfortunately, it's still not communicating with the ECU and i do not power the arduino with the pc but with a DC-DC converter. I tried the ECU emulator and it seems to work just fine (expected because the code was just the one Tom did).

I was exploring how to see in the pc the message recieved from the ECU (to see if there is anything at all), but I found that the baud rates are not compatible (10400 vs 9600). Then, I do not know how is it possible to print the message recieved by the ECU with another arduino.

The only thing that may not work is the L9637D itself, the funcion of which I do not know how to check.

In any case thanks for your help! If you have another suggestion or something I will be pleased to hear it.

Sure, it´s possible to see what´s happening there. And the Arduino can use 10400baud, like it does with the ECU Emulator. Since the IDE terminal does not support it, use f.e. HTerm instead.
But only on Hardware Serial! If you want to connect another Arduino with Software Serial, use something faster. SoftwareSerial can be used up to 57600 baud without any inconsistency.

There is a SerialPassthrough example on the Arduino IDE. But only with two hardware serials.
Change Serial1 to someting like

#include <SoftwareSerial.h>
SoftwareSerial SerialTwo(8, 7);

void setup() {

Now you can use the second Arduino connected to the PC via USB. Do not forget to twist the Rx to Tx and other way around. Differently to the L9637D.

Instead of a second Nano an USB TTL can do the job. But mine also does not support unusual baudrates.
A Logic Analyzer can handle any baud and also costs around 10$ or €.
Or an Oscilloscope. But a chap one does not translate anything to readable data. It can only show if there is anything on the line. And f.e. if the init sequences fits to exact 300ms and 25ms.

I know, this is pain in the a$$ to not see what´s happening on the line. That´s why I added a HC-06 and send anything to my phone. I introduced a switch to convert everything to OBD2 or just send out the raw data.

omg you are a genius sir

its working

i have tried with the diag code of the gear box indicator
"21 0B" and its working !
i can my gear position

i will update my post with other codes results

ps : very sorry for the delayed response to your post, my Z900 is my track bike and my track season started rigth after, i didnt have the time to mess with the obd

2104 for throttle position is working ( from 34 to DC )
2109 for rpm is working
210C for speed is working

from my testing it seems that all code from kds standard are working

now working in realdash
i need to make a config file now to script those command in this order

edit2: now the coding part start and its faster for me
here is a video of the gear indicator working in realdash
very thx for the help TriB, i can now make my custom dash for my z900 thank to you
as you can see in the video i made a template in realdash that look like a racing bike dash with only the info i needed ( rpm gauge at the top , speed on the left and gear indicator on the rigth , with a big lap timer on the middle )

1 Like