Teensy 4.0 / High Voltage Heater

Thanks for the photo's and the whole PDF.
I am relying on the work done by Open Inverter and their successful engagement of the heater and hot water in STM32. However, I think if we wanted to go back to scratch and do this the same way they did, we would need to write a sketch to do what they did:
So it seems that units firmware has been altered for the OEM. It runs on 9600 (as opposed to 19200 in the manual) and replies to IDs 22 and 23 (PID 0xD6 and 0x97) as opposed to IDs 24 and 40. Now remains the question which one is the control ID (35 in the manual) and is the same data format still used. I sent this on all valid and invalid IDs. Also added an extra 0 byte or an upcounter in the 4th byte. No change, never drawing power. Then I got desperate and brute forced all values from 0-0xFF with incrementing PID and frame length. As soon as the heater reports a status != 0 I stop. When I hit ID22 it stopped with a "Temporary lock" probably because of collision as 22 is a read command. There is one documented status field in the first 3 bits of PID 0x97/23. It is 0 in stop mode, then has various error values and when it is 4 we are in operate mode. I have never seen it change though! Except once when I had appended data to the ID 22 READ request. There is another fast changing field in the last byte of PID 0xD6/22. But it always toggles between the same values even if I just read data. I changed my brute force attack, now it just tried all permutations of 0,1,2,4,...,128 on all 4 bytes and on all PIDs with length ranging from 1 to 4. That went much quicker, 0-0xFF would have taken days. But no combination was found that would change the status to "Operate". I monitored the first diagnostic status byte while sending some random data to all PIDs. Then at PID 0x55/21 the bit jumped to the supposed "Temporary Lock" value. Same for 22 and 23. So since 21 doesn't return any data it must be the command message. Now it was easy. I increased the length from 3 to 4 and there was no more Temporary Lock when sending on PID 21. I put values into the 4 bytes until the heater turned on.
Now, to summarize:
- Only 12V and LIN needs to be connected, interlock needn't be connected to anything.
- Byte 0 sets the power with a scaling factor of 40
- Byte 1 set the temperature setpoint in °C with an offset of 40
- Byte 2 is unused
- Byte 3 =8 heater on (i.e. bit 3 set)
That may well be the only way to get out of this impasse but it's a LOT of reverse engineering stuff to do. And your units may even react differently...
I checked EBAY for a HVH50 unit in Europe but 460 euro's is too much for only satisfying my curiosity...

I would just like to get some response from the heater
Yeah, just some sign of life...

Paul
 
Hi Jordan,
Thought about a brute force method to see whether we can get any reaction from the HVH.
The following code writes 4 times to the "write power" register, with different adresses and different LIN checksums.
Then it tries to read from 4 times from the "read meas" register, again with different adresses and different LIN checksums.
I hope that 1 of these 4 reads reacts with a CRC...
You have to compile and run the code twice; either with 19200 baud or with 9600 baud.
C++:
#include "lin_bus.h"

LIN lin;

int lin_cs = 32;
int CRC = 0;

uint8_t linTXdata[4] = { 1, 85, 0, 8 };  // 40W, 45C, heater on
uint8_t linRXdata[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };

void setup() {
  pinMode(lin_cs, OUTPUT);
  digitalWrite(lin_cs, HIGH);  // enable MCP2004 LIN transceiver
  Serial.begin(115200);

  lin.begin(&Serial3, 19200);  // or lin.begin(&Serial3, 9600);
}

void loop() {
  lin.order(21, linTXdata, 4);  // set power, temp and heater ON
  delay(1);
  lin.order(21, linTXdata, 4, lin2x);
  delay(1);
  lin.order(35, linTXdata, 4);
  delay(1);
  lin.order(35, linTXdata, 4, lin2x);

  delay(100);  //wait for HVH to wakeup

  CRC = lin.response(22, linRXdata, 8);
  Serial.print("ID 22, lin1x, CRC: ");
  Serial.println(CRC, HEX);
  delay(10);

  CRC = lin.response(24, linRXdata, 8);
  Serial.print("ID 24, lin1x, CRC: ");
  Serial.println(CRC, HEX);
  delay(10);

  CRC = lin.response(22, linRXdata, 8, lin2x);
  Serial.print("ID 22, lin2x, CRC: ");
  Serial.println(CRC, HEX);
  delay(10);

  CRC = lin.response(24, linRXdata, 8, lin2x);
  Serial.print("ID 24, lin2x, CRC: ");
  Serial.println(CRC, HEX);
  delay(10);

  delay(1000);
}

I checked with the scope on the actual LIN wire. Frames are getting out as expected:
SDS00092.png


Paul
 
Excellent. I will give this a go and report back to you. My oscilloscope arrives tomorrow, so hopefully I will be able to share some screenshots that look like yours.
 
Found out that my scope also has a LIN decoder onboard...nice!

SDS00002.png

The scope can only list 7 frames at a time, but the 8th frame is also present.

Paul
 
Unfortunately, I am getting -1 responses still :(

Ran at both 9600 and 19200

I have one more heater to try that I ordered that was on a Volvo . . . since that might be the one used on OI. Also, I will try other wires on the harness, but the manual is pretty clear about which one is LIN in.out . . . so frustrating. HV is on and providing 300V of power. 12V and grounds are on and working . . . LIN signal is showing up . . .

1708573986958.png
 
Last edited:
Hi Jordan, you may want to uncomment line 463 in lin_bus.cpp [to be found here C:\Users\....\Documents\Arduino\Libraries\Teensy_3.x_4.x_and_LC_LIN_Master\src\] and compile & run the sketch again against the HVH.

Paul
 
I tested with the commented out line, but still just getting ffffffff in HEX or -1 without HEX.

1708622346246.png


1708622383667.png


I just wish there was a way to get any sign that the heater is receiving a LIN signal. Is the -1 a CRC error or coding issue or just no response from the HVH which could be a malfunction or power issue or something else?
 
Hi Jordan,
It may be wise to stick to the basic code that I listed in message #77 intially. Once we see a sign of life [a valid CRC] there, we can go back to your code.

Paul
 
My only question on that code is whether we need to run it through ALL IDs (1 to256) and not just 22 and 24 and order from 1 to 256 and not just 21 and 35.
 
Once we have determined that we do not see a valid CRC with all 19200/9600, 22/24, 21/35, lin1x/lin2x permutations, we can extend the code with writing and scanning all ID's. Let's take it step by step to avoid errors in the testcode.
At least we found a bug the library.

Paul
 
Ok, so I put your code back into the sketch and ran it in 9600 and 19200 and the result was FFFFFFFF.

1708648394662.png


in 9600 baud

1708648197068.png




In 19200 baud the readings were not the same on the write side:

1708648275206.png
 

Attachments

  • 1708648275409.png
    1708648275409.png
    1.7 MB · Views: 8
Last edited:
Just checked my code on 9600 baud and 19200 baud:

SDS00005.png


SDS00006.png


Identical as you can see.

Did you set the scope to 19200 baud?

Are you using exactly my code? I see from your IDE screenshot that void loop() starts at line 38. My code starts at line 18. So probably there is some more code in your void setup()?

Here is the code I used for testing at 9600 & 19200 baud:
C++:
#include "lin_bus.h"

LIN lin;

int lin_cs = 32;   // pin 23 for my LIN board
uint8_t linTXdata[4] = { 1, 85, 0, 8 };  // 40W, 45C, heater on
uint8_t linRXdata[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
uint8_t CRC =  0;

void setup() {
  pinMode(lin_cs, OUTPUT);
  digitalWrite(lin_cs, HIGH);  // enable MCP2004 LIN transceiver
  Serial.begin(115200);

  lin.begin(&Serial3, 19200);  // or lin.begin(&Serial3, 9600);
}

void loop() {
  lin.order(21, linTXdata, 4);  // set power, temp and heater ON
  delay(1);
  lin.order(21, linTXdata, 4, lin2x);
  delay(1);
  lin.order(35, linTXdata, 4);
  delay(1);
  lin.order(35, linTXdata, 4, lin2x);

  delay(100);  //wait for HVH to wakeup

  CRC = lin.response(22, linRXdata, 8);
  Serial.print("ID 22, lin1x, CRC: ");
  Serial.println(CRC, HEX);
  delay(10);

  CRC = lin.response(24, linRXdata, 8);
  Serial.print("ID 24, lin1x, CRC: ");
  Serial.println(CRC, HEX);
  delay(10);

  CRC = lin.response(22, linRXdata, 8, lin2x);
  Serial.print("ID 22, lin2x, CRC: ");
  Serial.println(CRC, HEX);
  delay(10);

  CRC = lin.response(24, linRXdata, 8, lin2x);
  Serial.print("ID 24, lin2x, CRC: ");
  Serial.println(CRC, HEX);
  delay(10);

  delay(1000);
}

Paul
 
I will try your code. I have been adding in the code to make the teensy still blink the amber light when compiled, but I guess I don't need to have that to run this code.

Also, the guy on Open Inverter said, "'I'd remove the code that sets heater power and only keep the code that queries the items (id 22 or 24, respectively). Only when you see the HVH responding to that will you have any chance to proceed further to send power requests."

I am not sure why we would would get a response without sending something first. Am I missing something?
 
I will try your code. I have been adding in the code to make the teensy still blink the amber light when compiled, but I guess I don't need to have that to run this code.
Yep, no need for the Interval timer and blinking LED. Keep the working software as simple as possible.
Also, the guy on Open Inverter said, "'I'd remove the code that sets heater power and only keep the code that queries the items (id 22 or 24, respectively). Only when you see the HVH responding to that will you have any chance to proceed further to send power requests."

I am not sure why we would would get a response without sending something first. Am I missing something?
I would be seriously surprised when the circuitry will react before sending a Power ON request first. That's how I would design it if I was a Webasto engineer...
Just leave the 4 lin.order() lines in. But you could comment out those 4 lin.order() lines if you feel encouraged.

Paul
 
Hi Paul. I was having some difficulty getting a LIN signal on that code but after a few tries, I did get a similar reading to you, but no response :(

1708724042153.png
1708724115994.png


1708724171778.png







1708724189893.png
 
Those bottom 2 screens are strange, ID 0x16? ID 0x18?
With respect to the scope: I used a time base of 2ms/div, and single-shot triggering on the trailing edge, trigger level around 6V.
You are not using the probes with 10x attenuation?

Paul
 
I am a novice with the oscilloscope. I just pushed auto adjust and tried to match your photo settings . . . not sure how to get get to 10x either. I can switch on the probe, but it stays as 1x on the scope. Also, not sure how to set those other items . . .
 
not sure how to get get to 10x either. I can switch on the probe, but it stays as 1x on the scope.

If your probes have a physical switch for 1X vs 10X, make sure you set it to 10X. The physical switch on the probe is the part that truly matters.

If your scope still is set for 1X, don't stress too much. The scope setting only adjusts the numbers you see on the screen. It just multiplies everything by 10. If the scope is set wrong, you'll just see numbers that are 1/10 of the correct value. So if touch 3.3V power with the probe, your scope will tell you it's 0.33 volts. But the waveform you see is correct. Only the scale is affected.

10X mode is almost always preferred, because it reduce loading your place onto your signals and it uses a combination of resistors and capacitors to couple the signal without attenuating high frequencies. 1X mode gives your scope the full voltage, but the bandwidth is terrible and your signal sees a higher load. Maybe 1X can be useful in some situation where you have very strong (low impedance) or low bandwidth signals measured in only millivolts. But for pretty much all other situations, use 10X mode.

Expensive probes usually do not have a 1X vs 10X switch. They're usually fixed at 10X. Some have an extra spring-loaded pin that gives a signal to the scope to tell it a 10X probe is connected. But cheaper probes and scopes rarely have this feature. Probes with a 1X vs 10X switch rarely (if ever) have a way to tell the scope which way you set the switch. You have to dive into a menu to configure the scope for how you set the switch. If you can't find that place, don't worry too much. Just know you need to add a zero onto the end of any voltage numbers your scope shows.
 
Thanks for all of that information Paul. The 0x18 is HEX for 24 and 0x16 is HEX for 22. I am leaving my scope on 1X because that is the only way I get a reading at the moment that matches your readings.

Sounds I like I need to probe all of the other ids to see if anything else brings a response, right?
 
I know it seems odd, but I can only get a signal on the scope with my extended code:

Code:
#include "lin_bus.h"

// Create an IntervalTimer object
IntervalTimer myTimer;

int ledState = LOW;                // ledState used to set the LED
unsigned long interval = 200000;   // interval at which to blinkLED to run every 0.2 seconds

LIN lin;

int lin_cs = 32;   // pin 23 for my LIN board
int led1 = 23;
int lin_fault = 28;

uint8_t linTXdata[4] = { 1, 85, 0, 8 };  // 40W, 45C, heater on
uint8_t linRXdata[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
uint8_t CRC =  0;
byte id = 0;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(lin_fault,INPUT);
  pinMode(lin_cs, OUTPUT);
  digitalWrite(lin_cs, HIGH);  // enable MCP2004 LIN transceiver
  digitalWrite(LED_BUILTIN, HIGH);

  Serial.begin(115200);

  //lin.begin(&Serial3, 19200);  // or
  lin.begin(&Serial3, 9600);
    delay(1000);
  pinMode(led1,OUTPUT);
    digitalWrite(LED_BUILTIN, LOW);

}

void loop() {
  lin.order(21, linTXdata, 4);  // set power, temp and heater ON
  delay(100);
  lin.order(21, linTXdata, 4, lin2x);
  delay(100);
  lin.order(35, linTXdata, 4);
  delay(100);
  lin.order(35, linTXdata, 4, lin2x);

  delay(100);  //wait for HVH to wakeup

  CRC = lin.response(22, linRXdata, 8);
  Serial.print("ID 22, lin1x, CRC: ");
  Serial.println(CRC, HEX);
  delay(100);

  CRC = lin.response(24, linRXdata, 8);
  Serial.print("ID 24, lin1x, CRC: ");
  Serial.println(CRC, HEX);
  delay(100);

  CRC = lin.response(22, linRXdata, 8, lin2x);
  Serial.print("ID 22, lin2x, CRC: ");
  Serial.println(CRC, HEX);
  delay(100);

  CRC = lin.response(24, linRXdata, 8, lin2x);
  Serial.print("ID 24, lin2x, CRC: ");
  Serial.println(CRC, HEX);
  delay(100);

  delay(1000);
}

the shorter version of the code does not generate a LIN signal on the scope:

Code:
#include "lin_bus.h"

LIN lin;

int lin_cs = 32;   // pin 23 for my LIN board
uint8_t linTXdata[4] = { 1, 85, 0, 8 };  // 40W, 45C, heater on
uint8_t linRXdata[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
uint8_t CRC =  0;

void setup() {
  pinMode(lin_cs, OUTPUT);
  digitalWrite(lin_cs, HIGH);  // enable MCP2004 LIN transceiver
  Serial.begin(115200);

  //lin.begin(&Serial3, 19200);  // or
  lin.begin(&Serial3, 9600);
}

void loop() {
  lin.order(21, linTXdata, 4);  // set power, temp and heater ON
  delay(1);
  lin.order(21, linTXdata, 4, lin2x);
  delay(1);
  lin.order(35, linTXdata, 4);
  delay(1);
  lin.order(35, linTXdata, 4, lin2x);

  delay(100);  //wait for HVH to wakeup

  CRC = lin.response(22, linRXdata, 8);
  Serial.print("ID 22, lin1x, CRC: ");
  Serial.println(CRC, HEX);
  delay(10);

  CRC = lin.response(24, linRXdata, 8);
  Serial.print("ID 24, lin1x, CRC: ");
  Serial.println(CRC, HEX);
  delay(10);

  CRC = lin.response(22, linRXdata, 8, lin2x);
  Serial.print("ID 22, lin2x, CRC: ");
  Serial.println(CRC, HEX);
  delay(10);

  CRC = lin.response(24, linRXdata, 8, lin2x);
  Serial.print("ID 24, lin2x, CRC: ");
  Serial.println(CRC, HEX);
  delay(10);

  delay(1000);
}

no idea why
 
Yeah, that's really odd.
The only differences are that the extended code creates an IntervalTimer object (that is not used anywhere) and drives the onboard LED.
And on my desk the 'short' code works and shows the correct LIN bus activity? I'm lost.

Paul
 
Me too! I am moving on to try an brute force every ID on send against every ID on response for something. I edited the code as set out below, but it is only doing a check of ID 1 order against ID 1 response.

1
1
FF

2
2
FF

3
3
FF

vs.

1
1
FF

1
2
FF

1
3
FF

If you have a suggestion to have it Order on 1 for every response ID then 2 for every response ID, I would be appreciative.

Code:
#include "lin_bus.h"

// Create an IntervalTimer object
IntervalTimer myTimer;

int ledState = LOW;                // ledState used to set the LED
unsigned long interval = 200000;   // interval at which to blinkLED to run every 0.2 seconds

LIN lin;

int lin_cs = 32;   // pin 23 for my LIN board
int led1 = 23;
int lin_fault = 28;

uint8_t linTXdata[4] = { 1, 85, 0, 8 };  // 40W, 45C, heater on
uint8_t linRXdata[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
uint8_t CRC =  0;
byte id = 0;
byte id2=0;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(lin_fault,INPUT);
  pinMode(lin_cs, OUTPUT);
  digitalWrite(lin_cs, HIGH);  // enable MCP2004 LIN transceiver
  digitalWrite(LED_BUILTIN, HIGH);

  Serial.begin(115200);

  //lin.begin(&Serial3, 19200);  // or
  lin.begin(&Serial3, 9600);
    delay(1000);
  pinMode(led1,OUTPUT);
    digitalWrite(LED_BUILTIN, LOW);

}

void loop() {

  if (id < 256 ) {id++;}

  //lin.order(21, linTXdata, 4);  // set power, temp and heater ON
  //delay(100);
  lin.order(id, linTXdata, 4, lin2x);
  delay(100);
  //lin.order(35, linTXdata, 4);
  //delay(100);
  //lin.order(35, linTXdata, 4, lin2x);

  //delay(100);  //wait for HVH to wakeup

  //CRC = lin.response(id, linRXdata, 8);
  //Serial.println(id);
  //Serial.println(CRC);
  //delay(100);

  //CRC = lin.response(24, linRXdata, 8);
  //Serial.print("ID 24, lin1x, CRC: ");
  //Serial.println(CRC, HEX);
  //delay(100);
  if (id2 < 256 ) {id2++;}

  CRC = lin.response(id2, linRXdata, 8, lin2x);
  //Serial.println(id2);
  //Serial.print("id2, lin2x, CRC: ");
  Serial.println(id);
  Serial.println(id2);
  Serial.println(CRC, HEX);
  delay(100);

  //CRC = lin.response(24, linRXdata, 8, lin2x);
  //Serial.print("ID 24, lin2x, CRC: ");
  //Serial.println(CRC, HEX);
  //delay(100);

  //delay(1000);
}
 
Back
Top