J1708 is a serial communication bus used on heavy-duty vehicles for engine control module communication.
| Parameter | Value |
|---|---|
| Baud rate | 9600 bps (default) |
| Data format | 8 data bits, 1 stop bit, no parity |
| Topology | Multi-drop (shared bus) |
| Maximum frame | 26 data bytes + MID + checksum |
| Bus idle | Minimum idle time between messages |
┌──────┬──────────────────────┬──────────┐
│ MID │ Data (1-26 bytes) │ Checksum │
│ 1B │ Variable │ 1B │
└──────┴──────────────────────┴──────────┘
J1587 defines the meaning of message data on top of J1708. Each message contains one or more PIDs (Parameter Identifiers) with their associated data.
| Range | Data Bytes | Description |
|---|---|---|
| 0-127 | 1 | Single-byte data |
| 128-191 | 2 | Double-byte data (little-endian) |
| 192-253 | Variable | Multi-byte: [PID][length][data...] |
| 254 | Variable | DDEC Unique (custom sub-protocol) |
| 255 | N/A | Page 2 selector (extends to PIDs 256-1511) |
┌─────┬──────┐
│ PID │ Data │
│ 1B │ 1B │
└─────┴──────┘
Example: PID 91 (Throttle Position), value = 125 (50.0% at 0.4% per bit)
┌─────┬────────┬────────┐
│ PID │ Low │ High │
│ 1B │ 1B │ 1B │
└─────┴────────┴────────┘
Little-endian: value = Low + (High << 8)
Example: PID 190 (RPM), Low=0xC0 High=0x0B = 3008 raw * 0.25 = 752 RPM
┌─────┬────────┬──────────────────┐
│ PID │ Length │ Data │
│ 1B │ 1B │ Length bytes │
└─────┴────────┴──────────────────┘
Example: PID 233 (Engine Serial), Length=8, Data="12345678"
When PID 255 appears at the start of a message, all following PIDs are offset by +256:
┌─────┬──────────────────────────────────┐
│ 255 │ Page 2 PIDs (add 256 to values) │
│ 1B │ Variable │
└─────┴──────────────────────────────────┘
Page 2 PID ranges:
A single J1708 frame can contain multiple PIDs concatenated:
┌─────┬──────┬─────┬──────┬──────┬─────┬────────┬──────────┐
│ MID │ PID1 │ D1 │ PID2 │ D2L │ D2H │ PID3 │ D3... │
│ 128 │ 91 │ 125 │ 190 │ 0xC0 │ 0x0B│ 233 │ len+data │
└─────┴──────┴─────┴──────┴──────┴─────┴────────┴──────────┘
| MID | Hex | Device |
|---|---|---|
| 128 | 0x80 | DDEC Master ECM |
| 175 | 0xAF | DDEC Receiver 1 |
| 183 | 0xB7 | DDEC Receiver 2 |
| 180 | 0xB4 | DIF Application |
DDEC engines use PID 254 as a wrapper for custom sub-protocols:
┌─────┬────────┬─────────┬──────────────────┐
│ 254 │ Length │ Sub-PID │ Data │
│ 1B │ 1B │ 1B │ (Length-1) bytes │
└─────┴────────┴─────────┴──────────────────┘
The Sub-PID identifies the specific DDEC operation:
| Sub-PID | Operation | Direction |
|---|---|---|
| 20 | Reset | App → ECM |
| 40 | Password | App → ECM |
| 140 | Status byte (slew/HP) | ECM → App |
| 150 | Engine info | ECM → App |
| 166 | Governor gain | Both |
| 195 | Status byte (calibration) | ECM → App |
| 196 | Injector calibration | ECM → App |
| 197 | Injector calibration set | App → ECM |
| 198 | Injector calibration data | ECM → App |
| 200 | Rating init | App → ECM |
| 202 | Slew command | App → ECM |
| 207 | VSG get | Both |
| 208 | VSG/Rating set | App → ECM |
| 209 | Rating get | ECM → App |
| 211 | Engine identification | ECM → App |
| 226 | Additional data | ECM → App |
| 244 | Software version | ECM → App |
| 321 | Channel reply (Page 2) | ECM → App |
To request specific PIDs from an ECM, send PID 0 followed by the PIDs to request:
App → ECM:
┌─────┬─────┬──────┬──────┬──────┐
│ MID │ 0 │ PID1 │ PID2 │ PID3 │
│ 180 │ │ 190 │ 110 │ 100 │
└─────┴─────┴──────┴──────┴──────┘
The ECM responds with the requested PID values.
Raw bytes from the bus go through a three-stage conversion:
1. Extract Raw Value
├── Uns8: raw = byte[0]
├── Int8: raw = (sbyte)byte[0]
├── Uns16: raw = byte[0] | (byte[1] << 8)
├── Int16: raw = (short)(byte[0] | (byte[1] << 8))
├── Uns32: raw = byte[0] | (byte[1]<<8) | (byte[2]<<16) | (byte[3]<<24)
├── Int32: raw = (int)(byte[0] | ...)
└── Bit: raw = (byte >> BitOffset) & ((1 << BitSize) - 1)
2. Apply Scale Factor
scaledValue = rawValue × PidInfo.ScaleFactor
3. Apply Conversion
displayValue = (scaledValue × PidInfo.ConvMult) + PidInfo.ConvAdd
Raw bytes: [0xC0, 0x0B]
DataType: Uns16
Raw value: 0x0BC0 = 3008
ScaleFactor: 0.25
ScaledValue: 3008 × 0.25 = 752.0
ConvMult: 1.0
ConvAdd: 0.0
DisplayValue: 752.0 RPM
Raw byte: [0xDC]
DataType: Uns8
Raw value: 220
ScaleFactor: 1.0
ScaledValue: 220.0
ConvMult: 1.0
ConvAdd: -40.0
DisplayValue: 220 - 40 = 180.0 DEG F
All calibration operations require a password first:
App → ECM (PID 254):
[254, 14, sub-PID, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30]
^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
oper 12 × ASCII '0' (0x30)
ECM → App (PID 254):
[MID, 254, 2, sub-PID, status]
^^^^^^
0x00 = accepted, other = rejected
Read request (PID 166 via PID 254):
App → ECM: [254, 2, 166, gainID]
gainID: 128=ReadOverall, 130=ReadProportional, 132=ReadIntegral, 134=ReadDifferential
ECM → App: [MID, 254, 4, 166, gainID, valueLow, valueHigh]
Set request:
App → ECM: [254, 4, 166, gainID, valueLow, valueHigh]
gainID: 129=SetOverall, 131=SetProportional, 133=SetIntegral, 135=SetDifferential
ECM → App: [MID, 254, 2, 166, status]
status: 0x00 = acknowledged
Gain scaling factors:
| Gain Type | Scale Factor | Example: raw=16 → value=1.0 |
|---|---|---|
| Overall | ÷16 | 16 / 16 = 1.0 |
| Proportional | ÷16384 | 16384 / 16384 = 1.0 |
| Integral | ÷1024 | 1024 / 1024 = 1.0 |
| Differential | ÷1048576 | 1048576 / 1048576 = 1.0 |
Get sequence:
Step 1: Password (sub-PID varies)
Step 2: Get
App → ECM: [254, 1, 207]
ECM → App: [MID, 254, 7, 207, minLo, minHi, altLo, altHi, maxLo, maxHi]
Set sequence:
Step 1: Password
Step 2: Set
App → ECM: [254, 7, 208, minLo, minHi, altLo, altHi, maxLo, maxHi]
ECM → App: [MID, 254, 2, 208, status]
Step 3: Complete confirmation
Values are 16-bit little-endian RPM values. Range: 400-2500 RPM.
Get sequence:
Step 1: InitPassword
Step 2: Get
App → ECM: [254, 1, 209]
ECM → App: [MID, 254, 2, 209, ratingIndex]
Set sequence:
Step 1: InitPassword
Step 2: RatingPassword
Step 3: Set
App → ECM: [254, 2, 208, ratingValue]
ECM → App: [MID, 254, 2, 208, status]
Step 4: Complete
Get codes:
App → ECM: [0, 194] (PID 0 data request for PID 194)
ECM → App: [MID, 194, length, count, code1_pid, code1_fmi, code2_pid, code2_fmi, ...]
FMI high bit: 0 = active, 1 = historical
Clear codes:
App → ECM: [195, 3, toMid, 0, codeType]
codeType: 0 = standard, 1 = extended
ECM → App: [MID, 196, length, status]
App → ECM: [254, length, 202, paramID, targetValueLo, targetValueHi]
ECM → App: [MID, 254, 2, 202, status]
status: 0x00 = acknowledged
23 slewable parameters are defined in the PID definition CSV (SlewableParam > 0).
Get sequence:
Step 1: Password
Step 2: Get
App → ECM: [254, 1, 196] (or dedicated PID)
ECM → App: [MID, 196, length, count, code1, code2, ..., code8]
8 injector codes per ECM
Set sequence:
Step 1: Password
Step 2: Set
App → ECM: [254, length, 197, code1, code2, ..., code8]
ECM → App: [MID, 254, 2, 197, status]
Step 3: Complete
Three reset types, sent in sequence:
App → ECM: [21] RESET_DDEC_ACTION
App → ECM: [22] RESET_REQUEST_TABLE
App → ECM: [23] RESET_DATA_TABLE
ECM → App: [MID, 140, status] for each
| Parameter | Value | Description |
|---|---|---|
| PID request interval | 11.5s | How often all monitored PIDs are re-requested |
| Interface status check | 1.5s | Connection health check |
| Worker loop sleep | 100ms | Time between message read cycles |
| UI update interval | 500ms | Display refresh rate |
| Default task timeout | 12s | Time before declaring task failure |
| CCO task timeout | 2s | Cylinder cutout timeout |
| Max retries | 3 | Retry attempts per task |
| Read sleep time | 20ms | Bus polling interval |
| Broadcast interval | 100ms | Simulator broadcast rate |