In the dotted line was the new set of parts to be incorporated to my build. The gauge cluster was upgraded from 7" to 12.6" to fully fill the area provided. Old setup:
New setup:
The CAN-portion
I had an Arduino Mega 2560 and an MCP2515 CAN-controller laying around since the vision was quite fermented already. I started with a schematic like this to ease my soldering process:
Hardware pictures:
Testing:
The code:
Code: Select all
#include <SPI.h>
#include <mcp2515.h> //https://github.com/autowp/arduino-mcp2515
#include <MegaCAN.h> //https://github.com/mantonakakis1/MegaCAN
//Sensor libraries
//Ambient temperature
#include <MBAmbientTemp.h>
AmbientTemp AT;
#define AT_EXE_INTERVAL 200 // How often Ambient Temperature is measured (ms)
unsigned long ATlastExecutedMillis = 0; // Variable to save the last executed time
//Oil Pressure
#include <MBOilPressure.h>
OilPressure OP;
#define OP_EXE_INTERVAL 50 // How often Oil Pressure is measured (ms)
unsigned long OPlastExecutedMillis = 0; // Variable to save the last executed time
//Fuel Amount
#include <MBFuelAmount.h>
FuelAmount FA;
#define FA_EXE_INTERVAL 100 // How often Fuel Amount is measured (ms)
#define FW_EXE_INTERVAL 100 // How often Fuel Warning is measured (ms)
unsigned long FAlastExecutedMillis = 0; // Variable to save the last executed time
unsigned long FWlastExecutedMillis = 0; // Variable to save the last executed time
//MegaCAN
#define CELSIUS // MegaCAN uses Fahrenheit as default. This transforms broadcasted temperatures to Celsius
//MegaCAN
const uint32_t baseID = 1512; // Must set to match Megasquirt Settings!
const uint32_t finalID = baseID + 17; // Must set to match Megasquirt Settings configured in TunerStudio! The last group of data broadcasted.
//MCP2515 related
struct can_frame receivedFrame;
MCP2515 mcp2515(10); //CS at D10
MegaCAN MegaCAN; // For processed Megasquirt CAN protocol messages
MegaCAN_message_t recMsgMSC; // Stores received message from Megasquirt, Megasquirt CAN protocol
MegaCAN_message_t respMsgMSC; // Stores response message back to Megasquirt, Megasquirt CAN protocol
MegaCAN_broadcast_message_t bCastMsg; // Stores unpacked Megasquirt broadcast data, e.g. bCastMsg.rpm
struct can_frame respMsg; // Actual response message back to Megasquirt, MSCAN protocol
uint16_t GPIOADC[8] = { 0 }; // Stores values to send to Megasquirt, 4 ADCS for each message
uint16_t adc0 = 0;
uint16_t adc1 = 0;
uint16_t adc2 = 0;
uint16_t adc3 = 0;
uint16_t adc4 = 0;
uint16_t adc5 = 0;
uint16_t adc6 = 0;
uint16_t adc7 = 0;
//Variables for averaging of sensor values
//AT
const int ATnumReadings = 10;
int ATreadings[ATnumReadings]; // The readings from the analog input
int ATreadIndex = 0; // The index of the current reading
int ATtotal = 0; // The running total
int ATaverage = 0; // The average
//OP
const int OPnumReadings = 10;
int OPreadings[OPnumReadings]; // The readings from the analog input
int OPreadIndex = 0; // The index of the current reading
int OPtotal = 0; // The running total
int OPaverage = 0; // The average
//FA
const int FAnumReadings = 100;
int FAreadings[FAnumReadings]; // The readings from the analog input
int FAreadIndex = 0; // The index of the current reading
int FAtotal = 0; // The running total
int FAaverage = 0; // The average
//FW
const int FWnumReadings = 100;
int FWreadings[FWnumReadings]; // The readings from the input
int FWreadIndex = 0; // The index of the current reading
int FWtotal = 0; // The running total
int FWaverage = 0; // The average
void initializeCAN() {
mcp2515.reset();
mcp2515.setBitrate(CAN_500KBPS, MCP_8MHZ); //Megasquirt specific 500kbs
mcp2515.setNormalMode();
}
void canMShandler(const can_frame &msg) {
// For Megasquirt CAN protocol, MS is requesting data:
if ((msg.can_id & CAN_EFF_FLAG) != 0) { //Data request from MS uses extended flag, there may be a better way to implement this with more advanced applications, works fine for sending data to MS GPIOADC
sendDataToMS(msg); //Due to the extended flag, we assume this is a MS data request and run the function to send data to MS, passing the message received from MS to the sendDataToMS function
}
// For Megasquirt CAN broadcast data:
else { //Broadcast data from MS does not use extended flag, therefore a standard message from MS will contain broadcast data
//Unpack megasquirt broadcast data into bCastMsg:
MegaCAN.getBCastData(msg.can_id, msg.data, bCastMsg); //baseID fixed in library based on const parameter entered for baseID above - converts the raw CAN id and buf to bCastMsg format
if (msg.can_id == finalID) {
/*~~~Final message for this batch of data, do stuff with the data - this is a simple example~~~*/
/*
Serial.print(bCastMsg.map); Serial.print(" | "); //should be kPa
Serial.print(bCastMsg.rpm); Serial.print(" | "); //should be rpm
Serial.println(bCastMsg.tps); //should be %
*/
}
}
}
void sendDataToMS(can_frame msg) {
MegaCAN.processMSreq(msg.can_id, msg.data, recMsgMSC); // Unpack request message ("msg") from MS into recMsgMS
if (recMsgMSC.core.toOffset == 2) { //For GPIOADC0-3
GPIOADC[0] = adc0; //Ambient Temperature
GPIOADC[1] = adc1; //Oil Pressure
GPIOADC[2] = adc2; //Fuel Amount
GPIOADC[3] = adc3; //Fuel Warning. ADC is not the best solution for an on/off, but will do
MegaCAN.setMSresp(recMsgMSC, respMsgMSC, GPIOADC[0], GPIOADC[1], GPIOADC[2], GPIOADC[3]); //Packs the GPIOADC0-3 values into "respMsgMSC"
}
else if (recMsgMSC.core.toOffset == 10) { //For GPIOADC4-7
GPIOADC[4] = adc4; //vacant
GPIOADC[5] = adc5; //vacant
GPIOADC[6] = adc6; //vacant
GPIOADC[7] = adc7; //vacant
MegaCAN.setMSresp(recMsgMSC, respMsgMSC, GPIOADC[4], GPIOADC[5], GPIOADC[6], GPIOADC[7]); //Packs the GPIOADC4-7 values into "respMsgMSC"
}
// Send response to Megasquirt using MSCAN protocol:
respMsg.can_id = respMsgMSC.responseCore | CAN_EFF_FLAG; //CAN_EFF_FLAG added to the end of response message, otherwise MS will not use it
respMsg.can_dlc = sizeof(respMsgMSC.data.response);
for (int i = 0; i < respMsg.can_dlc; i++) {
respMsg.data[i] = respMsgMSC.data.response[i];
}
mcp2515.sendMessage(&respMsg); //Sends the GPIOADC values stored in respMsg over CAN to Mesasquirt
//Serial.println("Data sent to Megasquirt");
}
void setup()
{
while (!Serial);
Serial.begin(115200);
Serial.println("MAP | RPM | TPS");
initializeCAN();
pinMode(33, INPUT); //Fuel warning indicator switch
//Setup Ambient Temp readings
for (int ATthisReading = 0; ATthisReading < ATnumReadings; ATthisReading++) //As long as thisReading is smaller than numReadings, add to thisReading
{
ATreadings[ATthisReading] = 0;
}
AT.init(A2,34); //Current from D34 and measuring voltage from A2
//Setup Oil Pressure readings
for (int OPthisReading = 0; OPthisReading < OPnumReadings; OPthisReading++) //As long as thisReading is smaller than numReadings, add to thisReading
{
OPreadings[OPthisReading] = 0;
}
OP.init(A0,35); //Current from D35 and measuring voltage from A0
//Setup Fuel Amount readings
for (int FAthisReading = 0; FAthisReading < FAnumReadings; FAthisReading++) //As long as thisReading is smaller than numReadings, add to thisReading
{
FAreadings[FAthisReading] = 0;
}
FA.init(A1,32); //Current from D32 and measuring voltage from A1
}
void loop() {
onReceived(); //Read messages from CAN-bus
getAT(); //Read Ambient Temperature and handle signal
getOP(); //Read Oil Pressure and handle signal
getFA(); //Read Fuel Amount and handle signal
getFW(); //Read Fuel Warning switch and handle signal
}
void onReceived()
//Listen to CAN-bus and run canMShandler after
{
if (mcp2515.readMessage(&receivedFrame) == MCP2515::ERROR_OK)
{
canMShandler(receivedFrame);
/*
Serial.println("Received message:");
Serial.println(" ID: 0x" + String(receivedFrame.can_id, HEX));
Serial.println(" DLC: " + String(receivedFrame.can_dlc));
Serial.println(" Data: " + String(receivedFrame.data[0]) + " " +
String(receivedFrame.data[1]) + " " +
String(receivedFrame.data[2]) + " " +
String(receivedFrame.data[3]) + " " +
String(receivedFrame.data[4]) + " " +
String(receivedFrame.data[5]) + " " +
String(receivedFrame.data[6]) + " " +
String(receivedFrame.data[7]));
*/
}
}
void getAT()
{
unsigned long ATcurrentMillis = millis(); // Record current time
if (ATcurrentMillis - ATlastExecutedMillis >= AT_EXE_INTERVAL) //If elapsed time from last execution is more than the specified interval
{
ATlastExecutedMillis = ATcurrentMillis; // save the last executed time
//AT
AT.measure(5,1024,10000,3007,3848,false); //float VCC,int _ADC, float R1, int R25, int Beta, bool prints
ATtotal = ATtotal - ATreadings[ATreadIndex]; // subtract the last reading
ATreadings[ATreadIndex] = AT.ambienttempvalue*10; // read from the sensor
ATtotal = ATtotal + ATreadings[ATreadIndex]; // add the reading to the total
ATreadIndex = ATreadIndex + 1; // advance to the next position in the array
if (ATreadIndex >= ATnumReadings) // if we're at the end of the array...
{
ATreadIndex = 0; // ...wrap around to the beginning
}
ATaverage = ATtotal / ATnumReadings; // calculate the average
adc0 = ATaverage; // Insert average to variable adc0
//Serial.println(adc0);
}
}
void getOP()
{
unsigned long OPcurrentMillis = millis(); // Record current time
if (OPcurrentMillis - OPlastExecutedMillis >= OP_EXE_INTERVAL) //If elapsed time from last execution is more than the specified interval
{
OPlastExecutedMillis = OPcurrentMillis; // save the last executed time
//OP
OP.measure(5,1024,100,10,69,129,184,false); //float VCC, int _ADC, float R1, int R_0BAR, int R_1BAR, int R_2BAR, int R_3BAR, bool prints
OPtotal = OPtotal - OPreadings[OPreadIndex]; // subtract the last reading
OPreadings[OPreadIndex] = OP.oilpressurevalue*100; // read from the sensor
OPtotal = OPtotal + OPreadings[OPreadIndex]; // add the reading to the total
OPreadIndex = OPreadIndex + 1; // advance to the next position in the array
if (OPreadIndex >= OPnumReadings) // if we're at the end of the array...
{
OPreadIndex = 0; // ...wrap around to the beginning:
}
OPaverage = OPtotal / OPnumReadings; // calculate the average
adc1 = OPaverage; // Insert average to variable adc1
//Serial.println(adc1);
}
}
void getFA()
{
unsigned long FAcurrentMillis = millis(); // Record current time
if (FAcurrentMillis - FAlastExecutedMillis >= FA_EXE_INTERVAL) //If elapsed time from last execution is more than the specified interval
{
FAlastExecutedMillis = FAcurrentMillis; // save the last executed time
//FA
FA.measure(5,1024,10,2,78,false); //float VCC,int _ADC, float R1, int R_Full, int R_Empty, bool prints
FAtotal = FAtotal - FAreadings[FAreadIndex]; // subtract the last reading
FAreadings[FAreadIndex] = FA.fuelamountvalue*100; // read from the sensor
FAtotal = FAtotal + FAreadings[FAreadIndex]; // add the reading to the total
FAreadIndex = FAreadIndex + 1; // advance to the next position in the array
if (FAreadIndex >= FAnumReadings) // if we're at the end of the array...
{
FAreadIndex = 0; // ...wrap around to the beginning
}
FAaverage = FAtotal / FAnumReadings; // calculate the average
adc2 = FAaverage; // Insert average to variable adc2
//Serial.println(adc2);
}
}
void getFW()
{
unsigned long FWcurrentMillis = millis(); // Record current time
if (FWcurrentMillis - FWlastExecutedMillis >= FW_EXE_INTERVAL) //If elapsed time from last execution is more than the specified interval
{
FWlastExecutedMillis = FWcurrentMillis; // save the last executed time
//FW
FWtotal = FWtotal - FWreadings[FWreadIndex]; // subtract the last reading
int sensorVal = digitalRead(33); // read port status:
if (sensorVal = LOW) // If the switch is closed...
{
FWreadings[FWreadIndex] = 1; //...reading is one
}
else
{
FWreadings[FWreadIndex] = 0; //...reading is zero
}
FWtotal = FWtotal + FWreadings[FWreadIndex]; // add the reading to the total
FWreadIndex = FWreadIndex + 100; // advance to the next position in the array
// if we're at the end of the array...
if (FWreadIndex >= FWnumReadings)
{
FWreadIndex = 0; // ...wrap around to the beginning
}
FWaverage = FWtotal / FWnumReadings; // calculate the average
adc3 = FWaverage; // Insert average to variable adc3
//Serial.println(adc3);
}
}
Fuel amount:
Oil pressure:
Ambient temperature:
This setup has been on my car for about 4 months now, with ~2000km driven and no problems except when the fuel tank is full to the brim, the resistance value is outside of my parameters resulting in the fuel gauge reading 0 for a while. It gets to 100 after spending some time on the happy pedal There is still some untapped potential, since I have a lot of I/O left for controlling relays and sending info to Megasquirt. Let's see what arises. Lastly, let's take a look on how the signals are handled in tunerstudio:
For every sensor you need to add a custom channel. Since you cannot send decimals to the CAN-bus, you need to multiply by ten on the Arduino side and divide by ten on TS side. Adding a gauge template is a good measure too.
That is all for now, feel free to comment!