Help with Lin bus connections

Hi everyone.

I am starting a project to use OEM auto parts in my PC simulator.
The parts I am trying to use are a Volvo V40 steering wheel and a VW Beetle electric window motor switch.
Both are Lin bus.

I have very little understanding of the lin bus protocol, so I've searched for success cases here in the forum.
I tried to replicate everything....but with no luck.
My guess is that I am failing the basic connection setup.

My wheel has the following connections:

Which I assume B and C are 12v and GND and A and D are the lin bus pin for each module.

I’ve tested with 3 different LIN transceivers:

I get nothing on the serial output using any of the 3.

I know that since this envolves no car, my arduino/lin trasnceiver needs to be set as master.

When using the first lin transceiver, I’ve set 5v to the SLP pin to wake the module.

I’ve also tested shorting LIN to INH.

Also tested shorting the arduino ground to lin ground.

No matter what I try…I never get any response.

I can post later my projects here (I am not at home at the moment) but, for now, I would only like to ask help confirming my connections.

I don’t know what I am doing wrong and any help would be most appreciated.

Thanks.

Regards,

mike

the master needs to send the LIN frame header

without the header, you will not see any communication I’m afraid….

sherzaad,

Thanks for your reply.

I’ve followed your help in other topics and seen also your Github LIN library.

I know that I need to send a frame header…but not exactly sure how. Does each device need it’s own unique header? If so….how can I find the one for my device?

Chatgpt suggested this:

Common LIN IDs for Volvo V40

While IDs can vary slightly depending on the year and trim (e.g., Adaptive Cruise Control vs. Standard), the most common IDs documented for this platform are:

  • 0x01 (or 0xC1 with parity bits): Typically used for the Right-hand keypad (Media, Volume, Next/Previous, Voice Control, OK/Thumbwheel).

  • 0x02 (or 0x42 with parity bits): Typically used for the Left-hand keypad (Cruise Control, Speed Limiter, ACC distance buttons).

lin_bus.sendHeader(0x01); // Dependent on your LIN library (e.g., MCP2004)

You have a LIN monitor example in your Github. I guess it will only work after the “wake up”, right?

Thanks again!

correct

you could loop thru the 255 pid’s to find which one’s your devices respond to ;)

bear in mind that the Master can also output a response (which you device MAY requires as input data).

you really should try, if possible, to monitor your device wen its actually connected in the correct system to get some insight…

hope that helps…

Thanks, sherzaad.

I will try during the weekend.

I can’t connect them to the correct system. The parts were bought individually. I have no Volvo or Beetle to connect to :slight_smile:

Thanks!

Regards,

mike

You might try to get a copy of the service manual for each of those units. You might be lucky and find a helpful dealer or mechanic.

Hi.

I’ve finally managed to get everything working :slight_smile:

I can read all buttons and set the lights on :smiley:

I am now ready to move to the next stage: make it 100% wireless.

My setup will be:

  • An ESP32-C6
  • A linbus transceiver (LINTTL3)
  • A MT3680 step-up converter
  • A 3.7v Li-Po battery

My question is regarding the MT3680 that I need to convert voltage to 12v (or the minimum accepted to make the lin bus work). I want it to power off when the ESP enters deep sleep.

I read that I also need something to stabilize the power output of the MT3680 because the lin bus is very sensible to power interference.

What extra resistors, transistors, capacitors or mosfets do I need to ensure that the MT3680 safely turns off and that it’s output is stable enough to feed the lin bus?

Thanks.

Regards,

Mike

Hi Mike,

Could you tell me how you pulled this off or perhaps share your code (here or on Github)? I am trying to make a 2005 (or maybe 2006) V70 steeringwheel working, but I am stuck. I am using an Arduino Mega 2560 with the green LINTTL3 board you also tried, but all I get are time-outs (0x04).

Thanks for sharing your knowledge.

It has been a lot of years, but if my memory is still working:

LIN is essentially a protocol layered on top of asynchronous serial. At the physical/data level it looks a lot like UART:

Start8 Data BitsStop

LIN adds master/slave control, frame format, synchronization, identifiers, checksums, and scheduling.

The BREAK is interesting and stalls a lot of designs. It is basically an intentionally long dominant (low) condition that normal UART framing would not generate.

I used to generate this with a port pin and an open collector transistor.

A LIN frame roughly becomes:

BREAKSYNC (0x55)IDENTIFIERDATA (0-8 bytes)CHECKSUM

It is used heavily in automotive applications mainly because it is less expensive than CAN.

Hopefully this helps:

Hi. Sure can.

It is not quite 100%, but it detects all the functions of the wheel (left and right modules) and turns the led lights on.

Are you sure your problem is in the code? Do you have all the connections right?

#include <HID-Project.h>

const int SLP_PIN = 14;
unsigned long lastLightTime = 0;

// Armazenamento para os dois lados (8 bytes cada)
byte lastDataEsq[8] = {0, 0, 0, 0, 0, 0, 0, 0}; 
byte lastDataDir[8] = {0, 0, 0, 0, 0, 0, 0, 0}; 

void setup() {
  delay(3000);
  Serial.begin(115200);
  Consumer.begin(); 
  pinMode(SLP_PIN, OUTPUT);
  digitalWrite(SLP_PIN, HIGH);
  Serial1.begin(9600);
  Serial.println("--- Monitorização Volante (Esq: 0x30 | Dir: 0x20) ---");
}

byte getPID(byte id) {
  byte p0 = ((id >> 0) ^ (id >> 1) ^ (id >> 2) ^ (id >> 4)) & 0x01;
  byte p1 = ~((id >> 1) ^ (id >> 3) ^ (id >> 4) ^ (id >> 5)) & 0x01;
  return id | (p0 << 6) | (p1 << 7);
}

void sendHeader(byte pid) {
  Serial1.end();
  pinMode(1, OUTPUT);
  digitalWrite(1, LOW);
  delayMicroseconds(1600);
  digitalWrite(1, HIGH);
  delayMicroseconds(100);
  Serial1.begin(9600);
  while(Serial1.available()) Serial1.read();
  Serial1.write(0x55);
  Serial1.write(pid);
}

void processarLIN(byte id, byte* lastData) {
  byte pid = getPID(id);
  sendHeader(pid);
  
  unsigned long t = millis();
  while(Serial1.available() < 8 && (millis() - t < 60));

  if (Serial1.available() >= 2) {
    byte currentData[8] = {0, 0, 0, 0, 0, 0, 0, 0};
    int idx = 0;
    while(Serial1.available() && idx < 8) {
      byte b = Serial1.read();
      if (b != 0x55 && b != pid) {
        currentData[idx++] = b;
      }
    }

    bool mudou = false;
    for(int i=0; i<8; i++) {
      if(currentData[i] != lastData[i]) mudou = true;
    }

    if (mudou) {
      Serial.print("ID 0x"); Serial.print(id, HEX); Serial.print(" -> ");
      for(int i=0; i<8; i++) {
        if(currentData[i] < 0x10) Serial.print("0");
        Serial.print(currentData[i], HEX);
        Serial.print(" ");
        lastData[i] = currentData[i];
      }
      Serial.println();
    }
  }
}

void loop() {
  // 1. MANTER LUZES (ID 0x10) - A cada 500ms
  if (millis() - lastLightTime > 500) {
    byte pidL = getPID(0x10);
    sendHeader(pidL);
    byte cmd[] = {0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    for(int i=0; i<8; i++) Serial1.write(cmd[i]);
    Serial1.write(calculateChecksum(pidL, cmd, 8, false));
    lastLightTime = millis();
  }

  // 2. LER LADO ESQUERDO (0x30)
  processarLIN(0x30, lastDataEsq);
  delay(10); // Pequena pausa entre pedidos

  // 3. LER LADO DIREITO (0x20)
  processarLIN(0x20, lastDataDir);
  
  delay(20);
}

byte calculateChecksum(byte pid, byte* data, int len, bool enhanced) {
  uint16_t sum = enhanced ? pid : 0;
  for (int i = 0; i < len; i++) {
    sum += data[i];
    if (sum > 255) sum -= 255;
  }
  return (byte)(~sum);
}