As far as I'm aware, the Volt heater runs on single-wire CAN ("GMLAN"). The code I have (untested) is below:
Code: Select all
/*
EberspaecherHeater.c
The heater communicates using J1939 protocol. It has to be "woken up" one time with a 0x100 message and then
must see a "keep alive" to stay active, which is the 0x621 message. The message repetition rate is between
25 and 100ms intervals.
The Eberspacher CAN version will work when used with a 33.33Kb SWCAN. The data below is the minimum required
to turn on the heater. It will operate at approximately 33% of full power. To command higher power, increase
the value of message 0x1072099 byte 1 (it begins with byte 0) which is 3E below.
Full power is applied when 85 is used as the value for byte 1. The power will vary based upon inlet
temperature as the PTC elements increase the resistance with higher temperature.
ID, Ext, LEN,D0,D1,D2,D3,D4,D5,D6,D7
0x100, False, 0, 00,00,00,00,00,00,00,00
0x621, False, 8, 00,40,00,00,00,00,00,00 - keep alive
0x13FFE060, True, 0, 00,00,00,00,00,00,00,00 - cmd1
0x10720099, True, 5, 02,3E,00,00,00,00,00,00 - control
0x102CC040, True, 8, 01,01,CF,0F,00,51,46,60 - cmd2
0x10242040, True, 1, 00,00,00,00,00,00,00,00 - cmd3
0x102740CB, True, 3, 2D,00,00,00,00,00,00,00 - cmd4
0x102740CB, True, 3, 19,00,00,00,00,00,00,00 - cmd5
*/
// CAN bus id's for frames sent to the heater
#define HCAN_WAKEUP 0x100 // wake up the device
#define HCAN_KEEP_ALIVE 0x621 // keep alive message
#define HCAN_CONTROL 0x10720099 // send power control message
#define HCAN_CMD1 0x13FFE060 // dummy message
#define HCAN_CMD2 0x102CC040 // dummy message
#define HCAN_CMD3 0x10242040 // dummy message
#define HCAN_CMD4 0x102740CB // dummy message
#define HCAN_CMD5 0x102740CB // dummy message
// CAN bus id's for frames received from the heater
//TODO: define correct can ID's, mask and masked id's
#define HCAN_STATUS 0x13FFE09D // receive status message 10011111111111110000010011101
#define CAN_MASK 0x0 // mask for above id's 00000000000
#define CAN_MASKED_ID 0x0 // masked id for id's from 0x258 to 0x268 00000000000
#define MAX_POWER_WATT 6000
bool heater_running = 0;
bool heater_request = 0;
uint16_t heater_power_request = 0; // value from 0 to 6000 watt
// uint8_t temp_heater; // in degree C
uint16_t heater_maxpower = 4000;
uint8_t heater_setpoint = 70;
uint8_t heater_derating_point = 55;
CAN_FRAME hcan_wakeup; // frame to send wake-up message
CAN_FRAME hcan_control; // frame to send control messages
CAN_FRAME hcan_keepalive; // frame to send heart beat
CAN_FRAME hcan_cmd1; // frame to send cmd1 message
CAN_FRAME hcan_cmd2; // frame to send cmd2 message
CAN_FRAME hcan_cmd3; // frame to send cmd3 message
CAN_FRAME hcan_cmd4; // frame to send cmd4 message
CAN_FRAME hcan_cmd5; // frame to send cmd5 message
CAN_FRAME hcan_incoming; // the frame sent to GEVCU containing status information
//if (status.analogIn[0] != 0) temp_heater = map(constrain(status.analogIn[0], 0, 2100), 0, 2100, 0, 100);
void setup_heater()
{
if (Can1.init(CAN_BPS_33333))
{
Serial.println("Heater CAN initialization completed.\n"); // Initialize CAN1 - Heater CAN
Can1.setNumTXBoxes(3);
}
else Serial.println("Heater CAN initialization (sync) ERROR\n");
for (int i = 0; i < 3; i++)
{
Can1.setRXFilter(i, 0, 0, true);
}
for (int i = 3; i < 7; i++)
{
Can1.setRXFilter(i, 0, 0, false);
}
// switch to normal mode on SW-CAN
digitalWrite(pin_swcan_mode, 1);
// 0x621, False, 8, 00,40,00,00,00,00,00,00 - keep alive
hcan_keepalive.length = 8;
hcan_keepalive.id = HCAN_KEEP_ALIVE;
hcan_keepalive.extended = 0;
hcan_keepalive.rtr = 0;
hcan_keepalive.data.byte[1] = 0x40;
// 0x13FFE060, True, 0, 00,00,00,00,00,00,00,00 - cmd1
hcan_cmd1.length = 8;
hcan_cmd1.id = HCAN_CMD1;
hcan_cmd1.extended = 1;
hcan_cmd1.rtr = 0;
// 0x102CC040, True, 8, 01,01,CF,0F,00,51,46,60 - cmd2
hcan_cmd2.length = 8;
hcan_cmd2.id = HCAN_CMD2;
hcan_cmd2.extended = 1;
hcan_cmd2.rtr = 0;
hcan_cmd2.data.byte[0] = 0x01;
hcan_cmd2.data.byte[1] = 0x01;
hcan_cmd2.data.byte[2] = 0xCF;
hcan_cmd2.data.byte[3] = 0x0F;
hcan_cmd2.data.byte[4] = 0x00;
hcan_cmd2.data.byte[5] = 0x51;
hcan_cmd2.data.byte[6] = 0x46;
hcan_cmd2.data.byte[7] = 0x60;
// 0x10242040, True, 1, 00,00,00,00,00,00,00,00 - cmd3
hcan_cmd3.length = 1;
hcan_cmd3.id = HCAN_CMD3;
hcan_cmd3.extended = 1;
hcan_cmd3.rtr = 0;
// 0x102740CB, True, 3, 2D,00,00,00,00,00,00,00 - cmd4
hcan_cmd4.length = 3;
hcan_cmd4.id = HCAN_CMD4;
hcan_cmd4.extended = 1;
hcan_cmd4.rtr = 0;
hcan_cmd4.data.value = 0;
hcan_cmd4.data.byte[0] = 0x2d;
// 0x102740CB, True, 3, 19,00,00,00,00,00,00,00 - cmd5
hcan_cmd5.length = 3;
hcan_cmd5.id = HCAN_CMD5;
hcan_cmd5.extended = 1;
hcan_cmd5.rtr = 0;
hcan_cmd5.data.value = 0;
hcan_cmd5.data.byte[0] = 0x19;
// 0x10720099, True, 5, 02,3E,00,00,00,00,00,00 - control
hcan_control.length = 5;
hcan_control.id = HCAN_CONTROL;
hcan_control.extended = 1;
hcan_control.rtr = 0;
}
void heater_cansend()
{
// map requested power (percentage) to valid range of heater (0 - 0x85)
hcan_control.data.byte[1] = map(constrain(heater_power_request, 0, MAX_POWER_WATT), 0, MAX_POWER_WATT, 0, 0x85);
Can1.sendFrame(hcan_keepalive);
Can1.sendFrame(hcan_cmd1);
Can1.sendFrame(hcan_control);
Can1.sendFrame(hcan_cmd2);
Can1.sendFrame(hcan_cmd3);
Can1.sendFrame(hcan_cmd4);
Can1.sendFrame(hcan_cmd5);
}
void run_heater() //do this every 60ms
{
heater_power_request = 0;
if (heater_request)
{
if (!heater_running)
{
//Wake up all SW-CAN devices by switching the transceiver to HV mode and sending the command 0x100 and switching the HV mode off again.
Serial.print("sending wake-up signal to heater");
digitalWrite(pin_swcan_mode, 0); // set HV mode
// 0x100, False, 0, 00,00,00,00,00,00,00,00
hcan_wakeup.length = 0;
hcan_wakeup.id = HCAN_WAKEUP;
hcan_wakeup.extended = 0;
hcan_wakeup.rtr = 0;
Can1.sendFrame(hcan_wakeup);
delay(5);
digitalWrite(pin_swcan_mode, 1); // set normal mode
heater_running = 1;
}
else
{
//Calculate the desired output power based on measured temperature.
if (heater_request && heater_running && (temp_heater <= heater_setpoint) && 0 /*pump q is running*/) //0 to force me to make sure pump q is programmed
{
// if below derating temperature, apply maximum power
if (temp_heater < heater_derating_point)heater_power_request = heater_maxpower;
// if between derating temp and target temp calculate derating of maximum power
else heater_power_request = map(temp_heater, heater_setpoint, heater_derating_point, 0, heater_maxpower);
}
heater_cansend();
}
}
else
{
heater_power_request = 0;
heater_cansend();
heater_running = 0;
}
}
The temp_heater variable is the heater temperature in C, using the thermistor inside the heater unit. I don't know the conversion for this thermistor.
All untested.
Again, untested.
I don't know which orange wire is + and which is -, this is a problem for me as I don't have a connector on the end of my wires, so can't look it up!
If someone is able to tell me which wire is which, that would be great. Mine do not have red/black bands on them.
If the power section is anything like the Leaf heater linked above, then polarity will matter.