DIY: Getting data from Holley CAN BUS
I just built a custom display for an endurance race car that needed to know how many gallons of gas has been used so far and how many are left in the tank. It reads the CAN BUS meant for the Racepak devices. This Holley CAN BUS protocol, aka HEFI 3rd Party CAN Communications Protocol, is public and published herestarting on page 92. There are 48 inputs and outputs like, RPM, boost, map, injector puleswidth, etc that are available through this interface. I would guess that any Holley ECM that supports Racepak can output these messages.
I used and arduino nano and an MCP2515 CAN BUS transceiver.
There are lots of uses for this information. The sky is the limit. You might want to drive a gauge cluster, relays, PWM devices, a very simple 4 digit LED display or even a custom DIY touchscreen like in my Vette:
Here is some sample code to read data from the Holley 3rd party CAN BUS protocol.
// test program, no license, open source, no warranty given
// reads canbus from Holley HEFI 3rd Party CAN Communications Protocol (RacePAK)
// about 100 times per second using an MCP2515 canbus module.
// libraries used to simplify using the can bus interface, display and EEprom memory
#include <Arduino.h>
#include <mcp_can.h>
#include <SPI.h>
const float versionNumber = 1.1; // so you can tell which version is burnt into the chip
// *********************** CAN related variables *****************
unsigned char CANLen = 0;
unsigned char buf[8];
#define CAN0_INT 2 // define CANbus interrupt pin
const int spiChipSelectPin = 10;
MCP_CAN CAN0(spiChipSelectPin); // Set CS pin
unsigned long lastTimeCANgotMsg; // to check for CANBUS timeouts
long unsigned int rxId;
// pins for MCP2515 CAN bus board to arduino nano
// Int D2
// SCK D13
// SI D11
// SO D12
// CS D10
// GND
// VCC
union Data { // overlay CAN payload with Long intgeger
unsigned char payloadArray[4]; // 8 byte payload area to the CAN BUS
unsigned long int payload;
};
int currRpm; // RPM xx,xxx
float currInjPulsewidth; // in milliseconds xx.x
float currTiming; // in degrees xx.x
int currMap; // in kPa xxx.x
float currBattery; // in volts xx.x
int currCoolant; // in F xxx
int lbsPerHourFromHolley; // in lbs/hour x,xxx
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void setup() { // runs once when device is powered up
Serial.begin(115200);
Serial.print(F("Holley CANbus Ver: ")); Serial.println(versionNumber);
Serial.println(F("Starting CANbus"));
// CANbus setup
while (CAN0.begin(MCP_ANY, CAN_1000KBPS, MCP_8MHZ) != CAN_OK) { // init CAN bus : baudrate = 1000k
Serial.println(F("CAN BUS Shield init fail"));
delay(500);
}
Serial.println(F("CAN BUS Shield init ok!"));
pinMode(CAN0_INT, INPUT);
CAN0.setMode(MCP_NORMAL); // Set operation mode to normal so the MCP2515 sends acks to received data.
} // end of setup
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void loop(){ // runs and loops forever
check_for_CAN_message(); // checks for next CAN message from Holley
check_for_CAN_timeout(); // checks for timeout in CAN messages
} // end of loop
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void check_for_CAN_message() {
if(!digitalRead(CAN0_INT)) { // If CAN0_INT pin is low, read receive buffer
CAN0.readMsgBuf(&rxId, &CANLen, buf); // Read data: len = data length, buf = data byte(s)
union Data data; // overlay 4 char buffer for conversion to unsigned long int
rxId = rxId & 0x7FFFF000; // filter out serial number .
switch ( rxId ) {
case 0x1E005000: // 0x1E005000 fuel packet
data.payloadArray[0] = buf[7]; // shift the data from CAN buffer to unsigned long
data.payloadArray[1] = buf[6];
data.payloadArray[2] = buf[5];
data.payloadArray[3] = buf[4];
lbsPerHourFromHolley = (int)(data.payload / 256); // remove Holley 256 multiplier
data.payloadArray[0] = buf[3]; // shift the data from CAN buffer to unsigned long
data.payloadArray[1] = buf[2];
data.payloadArray[2] = buf[1];
data.payloadArray[3] = buf[0];
currInjPulsewidth = (float)data.payload / 256.0;
break;
case 0x1E001000: //rpm packet
data.payloadArray[0] = buf[7]; // shift the data from CAN buffer to unsigned long
data.payloadArray[1] = buf[6];
data.payloadArray[2] = buf[5];
data.payloadArray[3] = buf[4];
currRpm = (int)(data.payload / 256);
break;
case 0x1E015000: //Timing packet
data.payloadArray[0] = buf[3]; // shift the data from CAN buffer to unsigned long
data.payloadArray[1] = buf[2];
data.payloadArray[2] = buf[1];
data.payloadArray[3] = buf[0];
currTiming = (float)data.payload / 256.0;
break;
case 0x1E019000: //MAP packet
data.payloadArray[0] = buf[3]; // shift the data from CAN buffer to unsigned long
data.payloadArray[1] = buf[2];
data.payloadArray[2] = buf[1];
data.payloadArray[3] = buf[0];
currMap = (int)(data.payload / 256);
break;
case 0x1E021000: //coolant packet
data.payloadArray[0] = buf[7]; // shift the data from CAN buffer to unsigned long
data.payloadArray[1] = buf[6];
data.payloadArray[2] = buf[5];
data.payloadArray[3] = buf[4];
currCoolant = (int)(data.payload / 256);
break;
case 0x1E025000: //Battery
data.payloadArray[0] = buf[7]; // shift the data from CAN buffer to unsigned long
data.payloadArray[1] = buf[6];
data.payloadArray[2] = buf[5];
data.payloadArray[3] = buf[4];
currBattery = (float)data.payload / 256.0;
break;
default: // debug printing to serial
return;
break;
lastTimeCANgotMsg = millis();
}// end switch
}// end CAN msg avail
} // end check_for_CAN_message
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void check_for_CAN_timeout() { // if a CAN failure occurs
if ( millis() - lastTimeCANgotMsg > 500 ) {
Serial.println(F("Canbus Timeout"));
lastTimeCANgotMsg = millis();
}
} // end check_for_CAN_timeout Last edited by LSswap; Jul 31, 2024 at 10:22 PM.
In any case here are all the listed items:
If one you want is not listed, you can probably send it to one of the user defined outputs and get it that way.
Last edited by Kippdipp; Aug 14, 2023 at 01:36 PM.
Do you have cruise control on your corvette using the Arduino, appears to be based on your video? I wasn’t aware there was anyway of sending canbus data inputs to the Holley.
Trending Topics
Btw, the vette does not have a holley. It has an AEM infinty which also has a publicy documented canbus and is read only.
I have not tried to write to the Holley can bus. The holley can bus projects I've developed only require read only for the time being.
The Best V8 Stories One Small Block at Time
Last edited by Kippdipp; Oct 27, 2023 at 11:34 PM.
https://youtube.com/shorts/k6B2xwK0djo?feature=share
I'm probably going to do something similar for my E36 LS swap. I have an E46 gauge cluster that is CAN driven and I'm using a MaxxEcu for engine management. I'll either have the MaxxEcu send BMW messages or I'll create an arduino based CAN converter.
Actually, there is no Holley solution for what was needed, so I made a custom unit for a road racing application to monitor fuel consumption.
I’m not an electronics expert, but I have built some things with raspberry pi’s, ESP32s, arduiono etc.
im going to start on this this week.
All the parts on the breadboard version are in the box, plus a voltage regulator. It adds up how much fuel has been used since the car was turned on. It can also count down from a preset fuel tank size (in this case it was 24 gallons). It can be calibrated and the countdown fuel tank size can be changed without having to reprogram the arduino. The switch changes from count up to count down mode.
Is this what you want to build?
Some things I have been thinking about are:
- Create my own Digital Dash type display
- Control physical dummy lights based on CTS and oil pressure values,
- Providing a speed signal and RPM on CAN Bus for a Prius ESP module if possible
When I developed the Holley CAN BUS gas consumption device, I didn't have a Holley. So instead, I built a simulator to simulate the Holley Canbus from the technical documents I linked above. You really need this when testing because you can't always safely test stuff in the car. For example, testing at 8000 RPM and high boost in the car, as you can imagine is not safe. So I'm going to post my simulator software that also runs on an Arduino nano and a MCP2515. It uses a couple of potentiometers to adjust the RPM and Fuel Flow Rate and a bunch of other variables are hard coded just to as a sanity check to make sure they can be read on the other end of the CAN BUS.
What this does is makes believe that it's the holley and transmits data over the canbus in a similar way that way that Holley would. A bunch of the other stuff it sends to the CAN BUS are fudged, but you can easily add more potentiometers to simulate more your choice of variables. The wiring for the simulator is pretty much the same as the diagram I provided above, except there are a couple of potentiometers added to some analog ports.
So basically, the simulator is connected to the thing you're trying to build over a twisted pair CAN BUS wires and as far as your device is concerned, it thinks a Holley is sending it data.
here's a picture of the simulator.
//Holley 3rd party CAN BUS simulator .. use freely at your own risk.. Arduino and MCP2515 board.
#include <SPI.h> // SPI library to communicate with the MCP2515 canbus board
#include <mcp_can.h> // CAN BUS library
#include <stdio.h>
#include <string.h>
#define CAN0_INT 2 // define which pin will be used for CAN BUS interrupt
const int spiChipSelectPin = 10;
MCP_CAN CAN0(spiChipSelectPin); // Set Chip Select pin. other SPI pins vary by Arduino board
float pct ; // the percentage (0 - 100) of each potentiometer
// arduino analog pins each tied to a 5k potentiometer
//int boostPin = A0;
int FlowRatePin = A5;
//int throttlePin = A2;
//int aitPin = A3;
//int coolantPin = A4;
//int afrPin = A5;
//int fuelPressPin = A6;
//int oilPressPin = A7;
//int oilTempPin = A8;
//int e85Pin = A9;
int rpmPin = A6;
int flowInt;
// flashing LED section
const int led = 6; // led is bitbanged to show status
const unsigned long flashLedDelayUL = 1000;
unsigned long flashLedTimeUL = 0;
unsigned char msg[8];
union Data {
unsigned char payloadArray[4]; // 8 byte payload area to the CAN BUS
unsigned long int payload;
};
void setup() {
Serial.begin(115200);
pinMode(CAN0_INT, INPUT);
pinMode(led, OUTPUT);
// canbus setup... led will blink 2/sec if fail to init canbus, otherwise on solid
while (CAN0.begin(MCP_ANY, CAN_1000KBPS, MCP_8MHZ) != CAN_OK) { // init can bus @ 1000k
digitalWrite(led, !digitalRead(led));
delay(500);
}
digitalWrite(led, HIGH);
CAN0.setMode(MCP_NORMAL);
Serial.println(0x01F0A000);
Serial.println((unsigned long)0x01F0A000);
Serial.println(0x1E001000);
Serial.println(0x1E005000);
/*
data.payload = 0;
Serial.println(data.payload);
Serial.println(data.msg[0], HEX);
Serial.println(data.msg[1], HEX);
Serial.println(data.msg[2], HEX);
Serial.println(data.msg[3], HEX);
data.payload = (unsigned long)7500 * (unsigned long)256;
Serial.println(data.payload);
Serial.println(data.msg[0], HEX);
Serial.println(data.msg[1], HEX);
Serial.println(data.msg[2], HEX);
Serial.println(data.msg[3], HEX);
*/
}
void loop() { // note delays between messages are not same as Infinty, but close enough
union Data data;
// rpm
data.payload = (unsigned long int)(((unsigned long)map(analogRead(rpmPin), 0, 1023, 0, 7500)) * 256);
msg[7] = data.payloadArray[0];
msg[6] = data.payloadArray[1];
msg[5] = data.payloadArray[2];
msg[4] = data.payloadArray[3];
CAN0.sendMsgBuf(0x1E001888, 1, 8, msg);
delayMicroseconds(50);
// 2180030464 0x01F0A000
// flow anywhere from 0 to 1018 per right pot
// 24 gals in 2 hours = 12 gals /hour = 72 /bs per hour
flowInt = analogRead(FlowRatePin) / 4;
flowInt = flowInt -5; // guarantee a 0 is possible
if (flowInt < 0) {
flowInt = 0;
}
data.payload = (unsigned long int)((float)flowInt * 256.0);
msg[7] = data.payloadArray[0];
msg[6] = data.payloadArray[1];
msg[5] = data.payloadArray[2];
msg[4] = data.payloadArray[3];
// injecter pulsewidth
data.payload = (unsigned long int)(5.5 * 256.0);
msg[3] = data.payloadArray[0];
msg[2] = data.payloadArray[1];
msg[1] = data.payloadArray[2];
msg[0] = data.payloadArray[3];
CAN0.sendMsgBuf(0x1E005888, 1, 8, msg);
delayMicroseconds(50);
//currTiming
data.payload = (unsigned long int)(31.2 * 256.0);
msg[3] = data.payloadArray[0];
msg[2] = data.payloadArray[1];
msg[1] = data.payloadArray[2];
msg[0] = data.payloadArray[3];
CAN0.sendMsgBuf(0x1E015888, 1, 8, msg);
delayMicroseconds(150);
// currcurrMap
data.payload = (unsigned long int)(125 * 256.0);
msg[3] = data.payloadArray[0];
msg[2] = data.payloadArray[1];
msg[1] = data.payloadArray[2];
msg[0] = data.payloadArray[3];
CAN0.sendMsgBuf(0x1E019888, 1, 8, msg);
delayMicroseconds(150);
//currCoolant
data.payload = (unsigned long int)(185.0 * 256.0);
msg[7] = data.payloadArray[0];
msg[6] = data.payloadArray[1];
msg[5] = data.payloadArray[2];
msg[4] = data.payloadArray[3];
CAN0.sendMsgBuf(0x1E021888, 1, 8, msg);
delayMicroseconds(150);
//currBattery
data.payload = (unsigned long int)(12.3 * 256.0);
msg[7] = data.payloadArray[0];
msg[6] = data.payloadArray[1];
msg[5] = data.payloadArray[2];
msg[4] = data.payloadArray[3];
CAN0.sendMsgBuf(0x1E025888, 1, 8, msg);
delayMicroseconds(150);
CAN0.sendMsgBuf(0x1E035888, 1, 8, msg);
delayMicroseconds(150);
CAN0.sendMsgBuf(0x1E035888, 1, 8, msg);
delayMicroseconds(150);
CAN0.sendMsgBuf(0x1E035888, 1, 8, msg);
delayMicroseconds(150);
CAN0.sendMsgBuf(0x1E035888, 1, 8, msg);
delayMicroseconds(150);
CAN0.sendMsgBuf(0x1E035888, 1, 8, msg);
delayMicroseconds(150);
CAN0.sendMsgBuf(0x1E035888, 1, 8, msg);
delayMicroseconds(150);
CAN0.sendMsgBuf(0x1E035888, 1, 8, msg);
delayMicroseconds(150);
CAN0.sendMsgBuf(0x1E035888, 1, 8, msg);
delayMicroseconds(150);
CAN0.sendMsgBuf(0x1E035888, 1, 8, msg);
delayMicroseconds(150);
CAN0.sendMsgBuf(0x1E035888, 1, 8, msg);
delayMicroseconds(150);
CAN0.sendMsgBuf(0x1E035888, 1, 8, msg);
delayMicroseconds(150);
CAN0.sendMsgBuf(0x1E035888, 1, 8, msg);
delayMicroseconds(150);
CAN0.sendMsgBuf(0x1E035888, 1, 8, msg);
delayMicroseconds(150);
if (millis() - flashLedTimeUL > flashLedDelayUL) {
digitalWrite(led, !digitalRead(led));
flashLedTimeUL = millis();
}
} Last edited by LSswap; Sep 26, 2023 at 11:30 PM.
I guess I should order a second Nano (I already have multiple MCP2515) to create a simulator Holley HP, and use the original one as device on the CAN Bus, correct?
Here's another one for a small audience: https://ls1tech.com/forums/fueling-i...n-testing.html
Here's another one for a small audience: https://ls1tech.com/forums/fueling-i...n-testing.html
Yes. I usually order them 10 at a time, Cheaper and nice to have on the shelf. I ordered a bunch when they were still $2.
LSswap, you provide so much great information. Thanks a ton for sharing! You helped me a ton on my last Arduino project. This is my next goal: To make a Holley can bus module to control my E fans, Fuel pumps, Meth Injection and save the Holley I/O for other things.
I know you don’t do this for money but I want to at least send you drink money or something. Check your PMs 😁
My first suggestion is since you look like you have a bunch of stuff to drive, look up a ULN2003. Seven drivers in one chip, built in kick back diode. Connects direct to the arduino port. Each driver is 500ma, so you can drive relays, or PWM to ground, etc. Saved me lots of wiring.






