Re: Mitsubishi Outlander PHEV heater
Posted: Thu Dec 29, 2022 6:29 pm
Got it working nicely now! Love the display output aswell - very useful!
openinverter Community
https://openinverter.org/forum/
Hm... you could readback the heater CAN temp byte4 and run the pump when temp is higher than X. It would require a transistor and a relay, but you would have a circulation system independant of heat source. I wanted to built similar heating myself. On one side Outlander heater run by a DUE uC and on the other Webasto fuel heater. I wanted to use Webasto pump to also run when Outlander heater would be ON.Turbopete wrote: ↑Thu Dec 29, 2022 8:04 pm It’s awesome and great work! I’d like to add a safety delay to switching off the water pump until the temp is back down to say 30 degrees C! Probably have to make it permantly live tho to ensure it runs even when the ignition is switched off. Either that or just get a delayed relay!
I like this idea - it will be my next mini project i think!arber333 wrote: ↑Thu Dec 29, 2022 9:52 pm Hm... you could readback the heater CAN temp byte4 and run the pump when temp is higher than X. It would require a transistor and a relay, but you would have a circulation system independant of heat source. I wanted to built similar heating myself. On one side Outlander heater run by a DUE uC and on the other Webasto fuel heater. I wanted to use Webasto pump to also run when Outlander heater would be ON.
Maybe an takeover relais construction would perform better. So if relais of pump is switched on everything is fed/switched from that relais too.arber333 wrote: ↑Thu Dec 29, 2022 9:52 pm Hm... you could readback the heater CAN temp byte4 and run the pump when temp is higher than X. It would require a transistor and a relay, but you would have a circulation system independant of heat source. I wanted to built similar heating myself. On one side Outlander heater run by a DUE uC and on the other Webasto fuel heater. I wanted to use Webasto pump to also run when Outlander heater would be ON.
Hi guysTurbopete wrote: ↑Tue Dec 20, 2022 5:17 pm Scratch that last question - i figured out that my code was still wrong! This is the code that i am using now:
Apologies for the untidyness - im learning!Code: Select all
#include <mcp_can.h> #include <SPI.h> #include <TaskScheduler.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 32 // OLED display height, in pixels #define OLED_RESET 4 // Reset pin # (or -1 if sharing Arduino reset pin) Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); #define INVERTPOT true #define OPENINVERTERCONTACTORS //uncomment to check can bus for Open Inverter opmode for contactors, prevents heating over precharge resistor #ifdef OPENINVERTERCONTACTORS unsigned long inverterLastRec; byte inverterStatus; #endif unsigned long temperatureLastRec; #define MAXTEMP 85 #define MINTEMP 40 unsigned int targetTemperature = 0; bool enabled = false; bool hvPresent = false; bool heating = false; int power = 20; int currentTemperature = 0; const int potPin = A0; const int ledPin = 3; long unsigned int canId; unsigned char len = 0; unsigned char buf[8]; char msgString[128]; // Array to store serial string #define CAN_INT 2 // Set INT to pin 2 MCP_CAN CAN(10); // Set CS to pin 10 void ms10Task(); void ms100Task(); void ms1000Task(); Task ms10(10, -1, &ms10Task); Task ms100(100, -1, &ms100Task); Task ms1000(1000, -1, &ms1000Task); Scheduler runner; void setup() { Serial.begin(115200); // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32 Serial.println(F("SSD1306 allocation failed")); for(;;); // Don't proceed, loop forever } displayOff(); Serial.println("Outlander Heater Control"); pinMode(ledPin, OUTPUT); //while (CAN_OK != CAN.begin(CAN_500KBPS, MCP_8MHz)) // init can bus : baudrate = 500k //while (CAN_OK != CAN.begin(MCP_ANY, CAN_500KBPS, MCP_8MHZ)) // Initialize MCP2515 running at 16MHz with a baudrate of 500kb/s and the masks and filters disabled. if(CAN.begin(MCP_ANY, CAN_500KBPS, MCP_8MHZ) == CAN_OK) // Serial.println("MCP2515 Initialized Successfully!"); Serial.println("CAN BUS Shield init ok!"); else // Serial.println("Error Initializing MCP2515..."); Serial.println("CAN BUS Shield init fail"); CAN.setMode(MCP_NORMAL); // Set operation mode to normal so the MCP2515 sends acks to received data. pinMode(CAN_INT, INPUT); // Configuring pin for /INT input // Serial.println("MCP2515 Library Receive Example..."); Serial.println("CAN BUS Shield init ok!"); runner.init(); runner.addTask(ms10); ms10.enable(); runner.addTask(ms100); ms100.enable(); runner.addTask(ms1000); ms1000.enable(); } void displayTemperature(void) { display.clearDisplay(); display.setTextSize(3); // Draw 2X-scale text display.setTextColor(SSD1306_WHITE); display.setCursor(20, 10); display.print(currentTemperature); display.print("/"); display.print(targetTemperature); if (power == 2) { display.fillRect(0, 0, 10, SCREEN_HEIGHT, SSD1306_WHITE); } else if (power == 1) { display.fillRect(0, SCREEN_HEIGHT/2, 10, SCREEN_HEIGHT, SSD1306_WHITE); } display.display(); // Show initial text } void displayOff(void) { display.clearDisplay(); display.setTextSize(3); // Draw 2X-scale text display.setTextColor(SSD1306_WHITE); display.setCursor(40, 10); display.println(F("Off")); display.display(); // Show initial text } void ms10Task() { //send 0x285 uint8_t canData[8]; canData[0] = 0x00; canData[1] = 0x00; canData[2] = 0x14; canData[3] = 0x21; canData[4] = 0x90; canData[5] = 0xFE; canData[6] = 0x0C; canData[7] = 0x10; CAN.sendMsgBuf(0x285, 0, sizeof(canData), canData); } void ms100Task() { int sensorValue = analogRead(potPin); if (INVERTPOT) { if (sensorValue < 923) { enabled = true; } else { enabled = false; } } else { if (sensorValue > 100) { enabled = true; } else { enabled = false; } } //if heater is not sending feedback, disable it, safety and that if (millis() - temperatureLastRec > 1000) { enabled = false; Serial.println("No Temperature recieved"); } if (INVERTPOT) { targetTemperature = map(sensorValue, 1023, 100, MINTEMP, MAXTEMP); } else { targetTemperature = map(sensorValue, 100, 1023, MINTEMP, MAXTEMP); } //send 0x188 #ifdef OPENINVERTERCONTACTORS bool contactorsClosed = inverterStatus == 0x01; if (!contactorsClosed) { enabled = false; } #else bool contactorsClosed = true; #endif digitalWrite(ledPin, enabled); if (enabled) { displayTemperature(); } else { displayOff(); } if (contactorsClosed && enabled && currentTemperature < targetTemperature) { uint8_t canData[8]; canData[0] = 0x03; canData[1] = 0x50; canData[3] = 0x4D; canData[4] = 0x00; canData[5] = 0x00; canData[6] = 0x00; canData[7] = 0x00; //switch to lower power when reaching target temperature if (currentTemperature < targetTemperature - 10) { canData[2] = 0xA2; power = 2; } else { canData[2] = 0x32; power = 1; } CAN.sendMsgBuf(0x188, 0, sizeof(canData), canData); } else { power = 0; } } void ms1000Task() { Serial.println("Heater Status"); Serial.print("HV Present: "); Serial.print(hvPresent); Serial.print(" Heater Active: "); Serial.print(heating); Serial.print(" Water Temperature: "); Serial.print(currentTemperature); Serial.println("C"); Serial.println(""); Serial.println("Settings"); Serial.print(" Heating: "); Serial.print(enabled); Serial.print(" Desired Water Temperature: "); Serial.print(targetTemperature); Serial.print(" Inverter: "); Serial.print(inverterStatus); Serial.println(""); Serial.println(""); } void loop() { // put your main code here, to run repeatedly: runner.execute(); //if(CAN_MSGAVAIL == CAN.checkReceive()) // check if data coming //{ //CAN.readMsgBuf(&canId, &len, buf); // read data, len: data length, buf: data buf //unsigned int canId = CAN.getCanId(); this line can be removed //if (canId == 0x398) { if(!digitalRead(CAN_INT)) // If CAN_INT pin is low, read receive buffer { CAN.readMsgBuf(&canId, &len, buf); // Read data: len = data length, buf = data byte(s) if((canId & 0x80000000) == 0x80000000) // Determine if ID is standard (11 bits) or extended (29 bits) sprintf(msgString, "Extended ID: 0x%.8lX DLC: %1d Data:", (canId & 0x1FFFFFFF), len); else sprintf(msgString, "Standard ID: 0x%.3lX DLC: %1d Data:", canId, len); //Serial.print(msgString); if((canId & 0x40000000) == 0x40000000){ // Determine if message is a remote request frame. sprintf(msgString, " REMOTE REQUEST FRAME"); //Serial.print(msgString); } else { for(byte i = 0; i<len; i++){ sprintf(msgString, " 0x%.2X", buf[i]); //Serial.print(msgString); } } // unsigned int canId = CAN.getCanId(); if (canId == 0x398) { //Heater status if (buf[5] == 0x00) { heating = false; power = 0; } else if (buf[5] > 0) { heating = true; } //hv status if (buf[6] == 0x09) { hvPresent = false; } else if (buf[6] == 0x00) { hvPresent = true; } //temperatures unsigned int temp1 = buf[3] - 40; unsigned int temp2 = buf[4] - 40; if (temp2 > temp1) { currentTemperature = temp2; } else { currentTemperature = temp1; } temperatureLastRec = millis(); } #ifdef OPENINVERTERCONTACTORS if (canId == 0x02) { inverterLastRec = millis(); inverterStatus = buf[0]; } #endif } #ifdef OPENINVERTERCONTACTORS // if(inverterLastRec + 500 < millis()) { // inverterStatus = 0; // } #endif }
I dont fuss with the pot. I simply use a digital input and internal CAN loop. I use this for the pump too. So when heater temp is lower than 50deg i request full power, when it is at 50deg i lower this to halfh and when heater reaches 55deg i terminate the request. This keeps heater output at 50deg in general which saves kWh acc to Q = m*c*dT. I may up the temp to 60deg if this will not be enough.Alibro wrote: ↑Sun Feb 04, 2024 5:07 pm I only had a 4k7 pot connected between ground and A0 so not sure if that is suitable as I think the heat requested was around 85C and it wouldn't drop below that. I think I could see a 10k pot in a previous photo from Jamie so I'll dig one out and give it a go. Does this sound correct?
My coding skills are virtually zero so now I have it working I think I'll leave well alone.arber333 wrote: ↑Sun Feb 04, 2024 5:31 pm I dont fuss with the pot. I simply use a digital input and internal CAN loop. I use this for the pump too. So when heater temp is lower than 50deg i request full power, when it is at 50deg i lower this to halfh and when heater reaches 55deg i terminate the request. This keeps heater output at 50deg in general which saves kWh acc to Q = m*c*dT. I may up the temp to 60deg if this will not be enough.
Like i said heater wants to go all the way to 85deg if you let it. Thats a waste of power. I wanted to use a limit to lower this to 55 as the kj are less there = less kWh.
Code: Select all
#include <mcp_can.h>
#include <SPI.h>
#include <TaskScheduler.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
#define OLED_RESET 4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
#define INVERTPOT true
#define OPENINVERTERCONTACTORS //uncomment to check can bus for Open Inverter opmode for contactors, prevents heating over precharge resistor
#ifdef OPENINVERTERCONTACTORS
unsigned long inverterLastRec;
byte inverterStatus;
#endif
unsigned long temperatureLastRec;
#define MAXTEMP 85
#define MINTEMP 40
bool enabled = false;
bool hvPresent = false;
bool heating = false;
int power = 20;
uint8_t Heatertemp = 0;
//const int potPin = A0;
const int Heater_pin = D14; //What is the equivalent digital input to A0?
const int ledPin = 3;
long unsigned int canId;
unsigned char len = 0;
unsigned char buf[8];
char msgString[128]; // Array to store serial string
#define CAN_INT 2 // Set INT to pin 2
MCP_CAN CAN(10); // Set CS to pin 10
void ms10Task();
void ms100Task();
void ms1000Task();
Task ms10(10, -1, &ms10Task);
Task ms100(100, -1, &ms100Task);
Task ms1000(1000, -1, &ms1000Task);
Scheduler runner;
void setup() {
Serial.begin(115200);
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}
displayOff();
Serial.println("Outlander Heater Control");
pinMode(ledPin, OUTPUT);
pinMode(Heater_pin,INPUT_PULLUP); // set Heater pin to input with using built in pull up resistor
//while (CAN_OK != CAN.begin(CAN_500KBPS, MCP_8MHz)) // init can bus : baudrate = 500k
//while (CAN_OK != CAN.begin(MCP_ANY, CAN_500KBPS, MCP_8MHZ))
// Initialize MCP2515 running at 16MHz with a baudrate of 500kb/s and the masks and filters disabled.
if(CAN.begin(MCP_ANY, CAN_500KBPS, MCP_8MHZ) == CAN_OK)
// Serial.println("MCP2515 Initialized Successfully!");
Serial.println("CAN BUS Shield init ok!");
else
// Serial.println("Error Initializing MCP2515...");
Serial.println("CAN BUS Shield init fail");
CAN.setMode(MCP_NORMAL); // Set operation mode to normal so the MCP2515 sends acks to received data.
pinMode(CAN_INT, INPUT); // Configuring pin for /INT input
// Serial.println("MCP2515 Library Receive Example...");
Serial.println("CAN BUS Shield init ok!");
runner.init();
runner.addTask(ms10);
ms10.enable();
runner.addTask(ms100);
ms100.enable();
runner.addTask(ms1000);
ms1000.enable();
}
void displayTemperature(void) {
display.clearDisplay();
display.setTextSize(3); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(20, 10);
display.print(currentTemperature);
display.print("/");
// display.print(targetTemperature);
if (power == 2) {
display.fillRect(0, 0, 10, SCREEN_HEIGHT, SSD1306_WHITE);
} else if (power == 1) {
display.fillRect(0, SCREEN_HEIGHT/2, 10, SCREEN_HEIGHT, SSD1306_WHITE);
}
display.display(); // Show initial text
}
void displayOff(void) {
display.clearDisplay();
display.setTextSize(3); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(40, 10);
display.println(F("Off"));
display.display(); // Show initial text
}
void ms10Task() {
//send 0x285
uint8_t canData[8];
canData[0] = 0x00;
canData[1] = 0x00;
canData[2] = 0x14;
canData[3] = 0x21;
canData[4] = 0x90;
canData[5] = 0xFE;
canData[6] = 0x0C;
canData[7] = 0x10;
CAN.sendMsgBuf(0x285, 0, sizeof(canData), canData);
}
void ms100Task() {
int sensorValue = analogRead(potPin);
// if (INVERTPOT) {
// if (sensorValue < 923) {
// enabled = true;
// } else {
// enabled = false;
// }
// } else {
// if (sensorValue > 100) {
// enabled = true;
// } else {
// enabled = false;
// }
// }
//if heater is not sending feedback, disable it, safety and that
if (millis() - temperatureLastRec > 1000) {
enabled = false;
Serial.println("No Temperature recieved");
}
// if (INVERTPOT) {
// targetTemperature = map(sensorValue, 1023, 100, MINTEMP, MAXTEMP);
// } else {
// targetTemperature = map(sensorValue, 100, 1023, MINTEMP, MAXTEMP);
// }
//send 0x188
#ifdef OPENINVERTERCONTACTORS
bool contactorsClosed = inverterStatus == 0x01;
if (!contactorsClosed) {
enabled = false;
}
#else
bool contactorsClosed = true;
#endif
digitalWrite(ledPin, enabled);
if (enabled) {
displayTemperature();
} else {
displayOff();
}
if (contactorsClosed && enabled) {
uint8_t canData[8];
if(digitalRead(Heater_pin) == LOW) { // if heater pin is ON
canData[0] = 0x03; }
else {
canData[0] = 0x00;
}
canData[1] = 0x50;
if(Heatertemp > 55) { // if temp is higher than 55deg
canData[2] = 0x00;
}
else if(Heatertemp > 50) { // if temp is higher than 50deg
canData[2] = 0x32; // seems to be current command (dec/10)
}
else {
canData[2] = 0xA2;
}
canData[3] = 0x40;
canData[4] = 0x00;
canData[5] = 0x00;
canData[6] = 0x00;
canData[7] = 0x00;
CAN.sendMsgBuf(0x188, 0, sizeof(canData), canData);
} else {
power = 0;
}
}
void ms1000Task() {
Serial.println("Heater Status");
Serial.print("HV Present: ");
Serial.print(hvPresent);
Serial.print(" Heater Active: ");
Serial.print(heating);
Serial.print(" Water Temperature: ");
Serial.print(Heatertemp);
Serial.println("C");
Serial.println("");
Serial.println("Settings");
Serial.print(" Heating: ");
Serial.print(enabled);
Serial.print(" Inverter: ");
Serial.print(inverterStatus);
Serial.println("");
Serial.println("");
}
void loop() {
// put your main code here, to run repeatedly:
runner.execute();
//if(CAN_MSGAVAIL == CAN.checkReceive()) // check if data coming
//{
//CAN.readMsgBuf(&canId, &len, buf); // read data, len: data length, buf: data buf
//unsigned int canId = CAN.getCanId(); this line can be removed
//if (canId == 0x398) {
if(!digitalRead(CAN_INT)) // If CAN_INT pin is low, read receive buffer
{
CAN.readMsgBuf(&canId, &len, buf); // Read data: len = data length, buf = data byte(s)
if((canId & 0x80000000) == 0x80000000) // Determine if ID is standard (11 bits) or extended (29 bits)
sprintf(msgString, "Extended ID: 0x%.8lX DLC: %1d Data:", (canId & 0x1FFFFFFF), len);
else
sprintf(msgString, "Standard ID: 0x%.3lX DLC: %1d Data:", canId, len);
//Serial.print(msgString);
if((canId & 0x40000000) == 0x40000000){ // Determine if message is a remote request frame.
sprintf(msgString, " REMOTE REQUEST FRAME");
//Serial.print(msgString);
} else {
for(byte i = 0; i<len; i++){
sprintf(msgString, " 0x%.2X", buf[i]);
//Serial.print(msgString);
}
}
// unsigned int canId = CAN.getCanId();
if (canId == 0x398) {
//Heater status
if (buf[5] == 0x00) {
heating = false;
power = 0;
} else if (buf[5] > 0) {
heating = true;
}
//hv status
if (buf[6] == 0x09) {
hvPresent = false;
} else if (buf[6] == 0x00) {
hvPresent = true;
}
//temperatures
unsigned int temp1 = buf[3] - 40;
unsigned int temp2 = buf[4] - 40;
if (temp2 > temp1) {
Heatertemp = temp2;
} else {
Heatertemp = temp1;
}
temperatureLastRec = millis();
}
#ifdef OPENINVERTERCONTACTORS
if (canId == 0x02) {
inverterLastRec = millis();
inverterStatus = buf[0];
}
#endif
}
#ifdef OPENINVERTERCONTACTORS
#endif
}