Who this is for
Haltech publishes CAN protocol documents that list every message its ECUs and devices put on the bus: which ID, which bytes, and how to scale the numbers. They're written for people integrating other hardware, such as a data logger, a microcontroller project or a third-party display. The documents all share the same conventions, and those conventions are what this article covers. For the actual message lists, see the Haltech CAN ECU broadcast protocol and the PD16 protocol.
The bus basics
| Property | Haltech CAN | What it means for your device |
|---|---|---|
| Bit rate | 1 Mbit/s | Your CAN controller has to be set to 1000 kbit/s or it won't see valid frames |
| Identifier length | 11-bit (standard) IDs | Don't configure extended 29-bit filters |
| How IDs are written | Hexadecimal | An ID in the document like 0x360 is 864 in decimal |
| Byte order | Big-endian | In a multi-byte value, the first byte holds the most significant part |
Each CAN frame carries up to eight data bytes. Haltech counts them from zero, so the first is byte 0 and the last is byte 7. Inside a byte, bit 7 is the most significant and bit 0 the least.
Reading the location notation
Every channel in a protocol table has a position that tells you exactly where its raw number lives in the frame. There are four forms you'll run into:
| Written as | Meaning | Example |
|---|---|---|
4 | One whole byte | The value is just byte 4, 0 to 255 |
2 - 3 | Several whole bytes, both ends included | A 16-bit value made from bytes 2 and 3 |
5:1 | One bit, written byte:bit | Bit 1 of byte 5, a single on/off flag |
6:3 - 7:0 | A run of bits that can cross a byte boundary, both ends included | A 12-bit value from bit 3 of byte 6 down to bit 0 of byte 7 |
Putting a multi-byte value together
Because the data is big-endian, the lower-numbered byte is the high byte. For a channel at bytes 2 - 3:
raw = (byte2 × 256) + byte3
So if byte 2 is 0x01 and byte 3 is 0xC8, the raw value is 256 + 200 = 456. A four-byte value at 4 - 7 works the same way, with byte 4 as the most significant. If the protocol lists a channel as signed, interpret the assembled number as two's complement before scaling it.
Pulling out a bit range
For a bit range, shift and mask. Using the 6:3 - 7:0 example: take bits 3 to 0 of byte 6 (four bits) as the top of the value and all eight bits of byte 7 as the bottom, giving 12 bits in total. In code that's ((byte6 & 0x0F) << 8) | byte7. Draw the bits out on paper the first time. It saves a lot of guessing.
From raw number to real units
Raw values are integers, so Haltech scales them to keep resolution. Each channel in the protocol table has a "Conversion from Raw" formula and a units column. In the formula, x is the raw number from the frame and y is the result in the listed units.
Take the raw value 456 we assembled above. If that channel were listed with a conversion of y = x / 10 and units of %, your device would display 45.6%. Some channels also carry an offset, for example a subtraction, so always apply the whole formula exactly as written.
The conversion only gets you to Haltech's units. If you want something else, like psi or Fahrenheit, your device has to do that step itself. The table below collects the conversions we use most often.
Units used in Haltech protocols
| Unit | Measures | Useful to know |
|---|---|---|
| RPM | Rotational speed | Usually engine (crankshaft) speed |
| kPa (abs) | Absolute pressure | 0 is a perfect vacuum, standard atmosphere is 101.3 |
| kPa (gauge) | Pressure relative to atmosphere | 0 is standard atmosphere, a perfect vacuum is -101.3 |
| % | Percentage | Throttle, duty cycles and similar |
| ° | Angle | One revolution is 360°; a full four-stroke cycle is 720° of crank rotation |
| lambda | Mixture | 1.0 is stoichiometric for whatever fuel is in use |
| K | Temperature | Kelvin steps are the same size as Celsius steps; 0 K is absolute zero |
| km/h | Speed | Divide by 1.609 for mph, or by 3.6 for m/s |
| m/s² | Acceleration | Divide by 9.8 for G |
| cc/min | Volume flow | The usual rating for injectors |
| cc and L | Volume | 1000 cc is 1 L; divide cc by 29.574 for US fl oz, and L by 3.785 for US gallons |
| ppm | Concentration | One million ppm is a pure substance |
| g/m³ | Density | Water is about one million g/m³ |
| V, dB | Voltage, decibels | Used as-is |
| ms, s, min, h | Time | 1000 ms per second, 60 s per minute, 60 min per hour |
Pressure is always absolute
Every pressure Haltech sends is absolute. That catches people out when a boost gauge shows 101 at idle with the engine off. To get gauge pressure, subtract 101.3 kPa. To get psi, divide kPa by 6.895. So a raw MAP reading that converts to 239 kPa (abs) is about 137.7 kPa gauge, or roughly 20 psi of boost.
Temperature arrives in Kelvin
- Celsius:
°C = K - 273.15 - Fahrenheit:
°F = (K - 273.15) × 9/5 + 32
As a sanity check, water freezes at 273.15 K and boils at 373.15 K. A coolant reading of 363.15 K is 90 °C, or 194 °F.
Lambda to AFR
Multiply lambda by the stoichiometric ratio of the fuel: 14.7 for gasoline, 9 for ethanol, 6.47 for methanol. A lambda of 0.80 on gasoline is 11.76:1 AFR. If you're running a flex fuel blend, remember the correct multiplier changes with ethanol content, which is one reason we prefer to display lambda.
Decoding bit fields
Some channels pack several yes/no states into one number. Each state is given its own power of two (1, 2, 4, 8 and so on), and the value sent is the sum of every state that's currently true. Haltech's "Engine Limiting Method" channel is a good example: fuel limiting is 1 and ignition limiting is 2. If both are active, the value is 3.
By hand
- Find the largest listed value that fits into the number you have. That state is on.
- Subtract it.
- Repeat with what's left until you reach zero.
Here's a made-up field to practice on: A = 1, B = 2, C = 4, D = 8, and the received value is 13. The largest that fits is 8, so D is on, leaving 5. Then 4 fits, so C is on, leaving 1. Then 1 fits, so A is on, leaving 0. States A, C and D are active and B is not.
With a calculator
Windows Calculator has a Programmer mode (open the menu and pick Programmer). Make sure it's in DEC, type the number, and read the BIN line. Reading right to left, the digits are worth 1, 2, 4, 8, 16 and so on, and every 1 is a state that's on. 13 shows as 1101: the 1, 4 and 8 positions are set.
In code
Test each flag with a bitwise AND, for example if (value & 0x02) to check whether the state worth 2 is set. It's faster and clearer than repeated subtraction.
Troubleshooting a CAN integration
| Symptom | Usually |
|---|---|
| No frames received at all | Wrong bit rate, CAN High and Low swapped, or missing termination |
| Values jump wildly or look huge | Bytes assembled in little-endian order instead of big-endian |
| Boost reads about 101 kPa with the engine off | Absolute pressure being shown as gauge; subtract 101.3 |
| Temperatures read around 300 | Kelvin not converted |
| A value is off by a factor of 10 or 1000 | Conversion formula skipped or applied twice |
CAN protocol FAQ
Can I add my own device to a Haltech CAN bus?
Yes, as long as it runs at 1 Mbit/s, uses 11-bit IDs and doesn't transmit on IDs Haltech devices already use. If you want more than one controller sending data, read multi-master CAN first.
What hardware do I need to connect?
A 4-port CAN hub and a Haltech CAN cable make it easy to tap into the bus without cutting the factory wiring. The Haltech CAN system overview explains how the network is laid out.
Where do I get the full message list?
Start with the broadcast protocol article linked above. If you need a specific protocol document for your project, contact us and we'll point you to the right version.




