Not sure where to start?
Copy this prompt into your AI assistant (ChatGPT, Claude, Gemini or similar). It will ask for your vehicle, your ECU and any CAN data you already have, then write the profile for you. Paste the result into the profile builder to check it.
You are helping me write a custom CAN profile for my KrispyGauge, a digital gauge that reads values from a CAN bus. A profile is a JSON file that tells the gauge where each value sits in the CAN frames and how to convert it.
Before writing anything, ask me:
1. The make, model and year of my vehicle, and the ECU or device I'm reading from (for example a Haltech Elite 2500, a Link G4X, an EGT module, or the car's own network).
2. Whether I already have CAN data for it: the ECU maker's CAN protocol document, a DBC file, an export from the ECU's tuning software, or a log from a CAN tool. Ask me to paste or upload whatever I have.
3. The bus speed, if I know it (125, 250, 500 or 1000 kbit/s).
4. Which values I want on the gauge (at most 32), such as RPM, coolant, oil pressure, boost, battery voltage.
Only use CAN IDs, byte positions and scaling from my documents or from official published documentation for my exact ECU. If you are not sure of a value, say so and ask me — never guess or invent a CAN ID or bit position. Tell me which channels you are unsure about.
Write the profile to these rules (full guide: https://krispygauge.com/docs/custom-can — JSON Schema: https://krispygauge.com/customCan/custom-can-profile.schema.json):
- One JSON object: "schema": 1, "name" (up to 47 characters), optional "author" (up to 31) and "notes", "bus": {"bitrate": 125000, 250000, 500000 or 1000000}, and "channels": a list of 1 to 32 channels.
- Each channel needs: "key" (unique, lower-case a-z, 0-9 and _ only, up to 23 characters), "name" (what the gauge shows, up to 15 characters), "id" (the CAN ID as a hex string like "0x360"), "order" ("motorola" for big-endian or "intel" for little-endian), "lsb_byte" (the byte holding the value's LEAST significant bit, 0-7, byte 0 is the first data byte), "lsb_bit" (0-7, 0 is the lowest bit) and "length" (1-32 bits).
- A 16-bit big-endian value in bytes n and n+1 has "lsb_byte": n+1, "lsb_bit": 0. DBC start bits for Motorola signals count from the MOST significant bit, so convert them; don't copy them across.
- Bits must fit in an 8-byte frame. IDs above 0x7FF need "extended": true. Add "signed": true for values that can be negative.
- Conversion: displayed value = raw × "scale" + "offset" ("scale" can't be 0). Or use "formula" (up to 31 characters, x is the raw value; + - * / % ^, brackets, abs sqrt ln log exp min(a,b) max(a,b)) instead of scale and offset.
- Always set "min" and "max" (max greater than min) for a sensible display range. Optional: "decimals" 0-3, "timeout_ms" (default 1000).
- Units matter: use exactly "rpm", "km/h", "°C", "kPa" (absolute pressure), "psi" or "bar" (gauge pressure) so the gauge converts them to my display settings. Convert to those base units: temperatures in °C (not °F or kelvin), speed in km/h, and oil/fuel/boost pressure as gauge pressure, not absolute. Other units such as "V", "%", "°" or "λ" are shown as received. Units are up to 7 characters.
- Multiplexed frames: add "mux": {"lsb_byte", "lsb_bit", "length", "equals"} to each channel that only applies when the selector has that value.
When you're done, give me the complete profile as one valid JSON code block, with no comments inside it, and a short list of any channels you weren't sure about. Remind me to paste it into the KrispyGauge profile builder at https://krispygauge.com/customCan to check it, and to compare the gauge's live values with my ECU software before I rely on them.
AI assistants make mistakes, especially with CAN IDs and bit positions. The best results come from giving it your ECU's CAN document or DBC file. Always compare the gauge's live values with your ECU software before you rely on them.
Each value the profile describes is called a channel. Once a profile is installed, its channels appear in the gauge's metric list as Custom 1, Custom 2 and so on, shown under the names you gave them. You can put them on any page, gauge face or alert, just like built-in metrics.
Your gauge only ever listens. A custom profile can't make the gauge transmit anything on your vehicle's bus.
- Installing a profile
- A complete example
- The file
- Channels
- Units
- Naming recommendations
- Keep keys stable
- Formulas
- Multiplexed frames
- Limits
- Checking your file
- Troubleshooting
- Trust and safety
Installing a profile
- Connect to your gauge's WiFi and open its web page.
- Go to Advanced → ECU / Protocol and select Custom CAN.
- Under Upload a profile, choose your
.jsonfile. - The page checks the file and shows a preview of every channel: name, CAN ID and bit position. If anything is wrong, it tells you which channel and why.
- Click Install on gauge.
- If the profile uses a different bus speed from the one the gauge is running, you'll be asked to reboot. Say yes. The new speed is applied at start-up.
After that, the live table shows every channel's current value. It's the quickest way to check a profile is right before you build pages around it.
You can Download JSON to back up the installed profile, or Remove it. Installing a new profile replaces the old one.
A complete example
You can also load this into the builder with Load example.
{
"schema": 1,
"name": "My ECU - engine data",
"author": "Your name",
"notes": "Connect to the ECU's CAN 1 at 1 Mbit/s.",
"bus": { "bitrate": 1000000 },
"channels": [
{
"key": "rpm",
"name": "RPM",
"unit": "rpm",
"id": "0x360",
"order": "motorola",
"lsb_byte": 1,
"lsb_bit": 0,
"length": 16
},
{
"key": "coolant",
"name": "Coolant",
"unit": "°C",
"id": "0x3E0",
"order": "motorola",
"lsb_byte": 1,
"lsb_bit": 0,
"length": 16,
"scale": 0.1,
"offset": -273.15,
"min": -40,
"max": 140
},
{
"key": "battery",
"name": "Battery",
"unit": "V",
"id": "0x372",
"order": "motorola",
"lsb_byte": 1,
"lsb_bit": 0,
"length": 16,
"scale": 0.1,
"min": 8,
"max": 16,
"decimals": 1
}
]
}
The file
A profile is one JSON object with these fields:
| Field | Required | What it is |
|---|---|---|
schema | yes | Always 1. |
name | yes | The profile's name, shown on the gauge's web page. Up to 47 characters. |
author | no | Who wrote it. Up to 31 characters. |
notes | no | Anything a reader of the file should know: which bus, which connector, which pins. It isn't stored on the gauge. |
bus.bitrate | yes | The bus speed in bits per second: 125000, 250000, 500000 or 1000000. It must match the bus you're connected to. |
channels | yes | A list of 1 to 32 channels. |
The gauge ignores fields it doesn't recognise. You can add your own (for example
"source": "...") and files written for newer gauges still load on older ones.
Channels
Each channel describes one value: which CAN frame it's in, where its bits sit in that frame, and how to turn those bits into a number.
| Field | Required | What it is |
|---|---|---|
key | yes | A short ID for this channel. Lower-case letters, digits and _, up to 23 characters, unique in the file. See Keep keys stable. |
name | yes | What the gauge shows. Up to 15 characters, so keep it short. |
unit | no | The unit, such as rpm, °C, kPa, V or %. Up to 7 characters. It matters more than it looks: see Units. |
id | yes | The CAN frame ID. Hex as a string ("0x360") or a plain number (864). |
extended | no | true for 29-bit (extended) IDs. Default false. |
order | yes | Byte order: "motorola" (big-endian) or "intel" (little-endian). |
lsb_byte | yes | The byte that holds the value's least significant bit. Byte 0 is the first data byte. |
lsb_bit | yes | Which bit of that byte is the least significant bit, 0–7, where 0 is the lowest bit. |
length | yes | How many bits the value uses, 1–32. |
signed | no | true if the value can be negative (two's complement). Default false. |
scale | no | Multiply the raw number by this. Default 1. Can't be 0. |
offset | no | Then add this. Default 0. |
min, max | no | The display range for dials and bars. If you leave them out, the gauge uses the full range the value can produce, which is often far wider than you want. Set them. |
decimals | no | Decimal places shown, 0–3. Default 0. |
timeout_ms | no | If the frame doesn't arrive within this many milliseconds, the channel shows "no data" instead of a stale value. Default 1000. The gauge also treats anything older than 2 seconds as stale, so values above 2000 act as 2000. |
formula | no | Replaces scale and offset with a calculation. See Formulas. |
mux | no | For multiplexed frames. See Multiplexed frames. |
How the value is worked out
displayed value = raw × scale + offset
For example, a coolant temperature sent as tenths of a kelvin uses
"scale": 0.1, "offset": -273.15. A raw value of 3631 then becomes
3631 × 0.1 − 273.15 = 90 °C.
Finding the bits
Most ECU documentation and DBC files tell you the start byte, length and byte order. The builder's bit map shows exactly which bits a channel reads, and its Try it box decodes a frame you paste in. Some common cases:
A 16-bit big-endian value in bytes 0 and 1 (very common, e.g. Haltech, Link, MoTeC). The low byte is byte 1:
"order": "motorola", "lsb_byte": 1, "lsb_bit": 0, "length": 16
In general, a big-endian value in bytes n and n+1 uses lsb_byte: n+1.
A 16-bit little-endian value in bytes 2 and 3. The low byte comes first:
"order": "intel", "lsb_byte": 2, "lsb_bit": 0, "length": 16
A single bit or a small field. A 4-bit value in the top half of byte 7:
"order": "intel", "lsb_byte": 7, "lsb_bit": 4, "length": 4
Coming from a DBC file? DBC "start bits" for Motorola signals count from the
most significant bit, so they aren't the same as lsb_byte/lsb_bit.
Don't copy the start bit across: use Import DBC in the
profile builder, which converts it for you, along with the
scale, offset, range and units.
Units — get these right
The unit field decides how the gauge treats a channel. Use exactly the
spellings below and the channel behaves like the gauge's built-in metric of that kind. Use anything
else and the value is shown exactly as received.
The name is just a label, so "Engine RPM" and "RPM" behave the same. The unit is what counts.
| Write the unit as | The gauge treats it as | What you get |
|---|---|---|
rpm | Engine speed | Follows your Max RPM and Redline settings and uses the same dial ticks as the built-in RPM, so tachometers and redline zones look right. |
km/h | Road speed | Converted by the Speed setting (km/h, mph, m/s, knots) and follows Max Speed. |
°C | Temperature | Converted by the Temperature setting (°C / °F). |
kPa | Pressure, absolute | Converted by the Pressure setting. Use for MAP, baro and anything else measured against a vacuum. |
psi | Pressure, gauge | Converted by the Pressure setting. Use for oil, fuel, coolant and boost pressure measured against atmosphere. |
bar | Pressure, gauge | As psi. |
anything else (V, %, °, A, L, λ…) | Shown as received | No conversion. |
Always write the profile in these base units, whatever you prefer to see. Each driver then chooses their own display units in the gauge's settings, and one profile works for everyone.
-
Temperatures in °C, not °F. A channel in
°Fis shown as received and won't follow the Temperature setting. If the ECU sends Fahrenheit, convert it with a formula:"formula": "(x-32)/1.8","unit": "°C". - Speeds in km/h, not mph. If the ECU sends mph, use
"scale"× 1.609344. -
Pressures: mind absolute vs gauge. If the ECU sends oil pressure as absolute kPa,
subtract atmosphere and convert to psi. Then 0 means "no pressure" and the Pressure setting's
kPa(a) mode adds atmosphere back correctly:
(That is (raw × 0.1 − 101.3) kPa × 0.1450377 for an ECU sending tenths of a kPa absolute.)"unit": "psi", "scale": 0.01450377, "offset": -14.69232 - Use
°Cwith the degree sign, exactly as shown.C,degCordeg Cwon't be recognised.
Naming recommendations
Names are labels, but consistent names make pages easier to build and profiles easier to share. Up to 15 characters; longer names are shortened.
| Measurement | Suggested name | Unit |
|---|---|---|
| Engine speed | RPM |
rpm |
| Road speed | Speed |
km/h |
| Coolant temperature | Coolant |
°C |
| Intake air temperature | Intake Air |
°C |
| Oil temperature | Oil Temp |
°C |
| Oil pressure | Oil Press |
psi |
| Fuel pressure | Fuel Press |
psi |
| Manifold pressure | MAP |
kPa |
| Boost | Boost |
psi |
| Throttle position | TPS |
% |
| Battery voltage | Battery |
V |
| Lambda | Lambda |
(none) |
| AFR | AFR |
(none) |
| Ignition timing | Ign Angle |
° |
| ECU temperature | ECU Temp |
°C |
| Gear | Gear |
(none) |
Keep keys stable
When you upload a new version of a profile, each key stays on the same Custom
slot it had before. A key that was Custom 3 is still Custom 3, so the user's pages and alerts keep
showing the same thing.
- Don't rename keys between versions of a profile. Change
nameas much as you like. - Removing a channel frees its slot. A new key added later may take it.
Formulas
Sometimes raw × scale + offset isn't enough. A formula replaces it with a calculation, where x is the raw value:
"formula": "(x*0.1-40)*9/5+32"
- Up to 31 characters.
- Operators:
+ - * / % ^and brackets. - Functions:
abs() sqrt() ln() log() exp() min(a,b) max(a,b). - When a formula is present,
scaleandoffsetare ignored. - A formula that doesn't make sense (a divide by zero, the square root of a negative) shows "no data" rather than a wrong number.
- The gauge checks every formula when you install the profile. A typo stops the upload rather than failing silently.
Multiplexed frames
Some devices send different data in the same frame ID, using one byte (the selector) to say
which set it is. EGT modules and some dash streams work this way. Give each channel a mux
saying which selector value it belongs to:
{ "key": "egt_1", "name": "EGT 1", "unit": "°C",
"id": "0x700", "order": "motorola", "lsb_byte": 2, "lsb_bit": 0, "length": 16,
"mux": { "lsb_byte": 0, "lsb_bit": 0, "length": 8, "equals": 1 } },
{ "key": "egt_2", "name": "EGT 2", "unit": "°C",
"id": "0x700", "order": "motorola", "lsb_byte": 2, "lsb_bit": 0, "length": 16,
"mux": { "lsb_byte": 0, "lsb_bit": 0, "length": 8, "equals": 2 } }
The selector uses the same byte order as the channel. The channel only updates when the selector matches.
Limits
| Channels per profile | 32 |
| Profiles on the gauge | 1 (installing replaces) |
| Channel name | 15 characters |
| Unit | 7 characters |
| Key | 23 characters: a-z, 0-9, _ |
| Profile name | 47 characters |
| Formula | 31 characters |
| Value width | 1–32 bits |
| Frame size | Classic CAN, up to 8 bytes (no CAN-FD) |
| Precision | Values are stored as single-precision numbers, so raw values above about 16 million lose their last digits. |
The installed profile is stored alongside your Personalisation images and uses 4 KB of that space. While a profile is installed, you can store 15 images instead of 16.
Checking your file
The profile builder runs these same checks as you type. The gauge rejects a file, and tells you which channel is at fault, if:
- it isn't valid JSON, or is over 64 KB
schemaisn't1- there are no channels, or more than 32
- a
keyis missing, repeated, too long, or uses characters other thana-z 0-9 _ - a channel's bits run past the end of an 8-byte frame
lengthisn't 1–32scaleis0, or a number isn't a real numbermaxisn't greater thanmindecimalsisn't 0–3- an
idis above0x7FFwithout"extended": true, or above0x1FFFFFFF bitrateisn't one of the four supported speeds- a
formuladoesn't parse
It accepts the file but warns you when a name or unit has been shortened, or the file
uses values (text for raw values), which isn't supported yet.
A frame that arrives shorter than a channel needs isn't an error. That channel just shows "no data" until a full frame arrives.
Writing profiles with an AI assistant: start with the prompt at the top of this page, and give it your ECU's CAN document or DBC file. It can also use the JSON Schema. Paste the result into the builder to check it, then install it and compare the live table with your ECU software before relying on it.
Troubleshooting
| What you see | Likely cause |
|---|---|
| Every channel shows "—" | Wrong bus speed (check bitrate and reboot when asked), wrong wiring or termination, or the gauge isn't set to Custom CAN. |
| One channel shows "—" | Wrong id, extended set wrongly, or the frame is shorter than the channel needs. |
| A number that jumps around wildly | Wrong order, or lsb_byte is off by one. |
| Roughly right but 10× or 100× out | Wrong scale. |
| Negative numbers show as huge positives | Add "signed": true. |
| A temperature reads −273 | The ECU is sending 0 for it. Usually no sensor is fitted (0 kelvin is −273.15 °C). |
| Temperature or speed won't follow the Units setting | The unit isn't exactly °C / km/h. See Units. |
| The tachometer ignores Max RPM / Redline | The unit isn't exactly rpm. |
Trust and safety
- Profiles from the community haven't been checked against a real vehicle unless we say so. The gauge shows every profile as community / unverified. Profiles in the KrispyDigital library are marked as tested when we've tested them on a vehicle.
- Always compare a new profile's live values against your ECU's own software before trusting it for warnings or alerts.
- A profile is data only. It can't run code, and it can't make the gauge transmit.