![]()
一、协议概述
SAE J1850 是由美国汽车工程师学会(SAE)于1994年颁布的车载通信协议标准,旨在统一车载诊断系统(OBD)的通信规范。该协议属于Class B类总线标准(速率20–125kbps),采用非破坏式冲突解决机制(CSMA/CR)与CRC校验技术。
J1850 协议在实际应用中形成了两种物理层实现:
特性
J1850 PWM
J1850 VPW
传输方式
双线差分(Bus+ / Bus-)
单线(Bus+)
速率
41.6 kbps
10.4 kbps
电压
5V 差分
7.5V
主要应用
Ford、Mazda
GM(通用汽车)
J1850 PWM 被福特汽车采用,称为 Standard Corporate Protocol(SCP)。部分马自达(Mazda)产品以及三菱(Mitsubishi)的部分车型也有参考应用。该协议通过 OBD-II 接口的 Pin 2(Bus+) 和 Pin 10(Bus-) 进行双线差分通信。
注意:由于 J1850 协议在物理层和数据帧格式上存在厂商差异,福特、通用和克莱斯勒三种实现互不兼容。该协议已逐渐被 CAN 总线取代,新车型不再使用,仅在旧车维修中需要兼容。二、物理层与信号编码 2.1 双线差分信号
J1850 PWM 采用两条信号线(Bus+ 和 Bus-)进行差分传输,两条线上的信号互为反向镜像。这种设计具有更好的抗共模干扰能力,即使其中一条线出现故障,总线仍可能继续通信。
差分高电平 :+5V(Bus+ 为高,Bus- 为低)
差分低电平 :-5V(Bus+ 为低,Bus- 为高)
逻辑高电平范围:4.25V ~ 20V
逻辑低电平阈值:低于 3.5V
J1850 PWM 的每个位周期固定为 24 μs,通过高电平持续时间的长短来区分逻辑 0 和逻辑 1:
逻辑值
位周期
高电平持续时间
低电平持续时间
逻辑 0
24 μs
16 μs
8 μs
逻辑 1
24 μs
8 μs
16 μs
时序参数的标准值如下:
参数
含义
标称值
发送容差
接收容差
Tp1
逻辑 1 高电平持续
8 μs
7–9 μs
6–11 μs
Tp2
逻辑 0 高电平持续
16 μs
15–17 μs
14–19 μs
Tp3
位周期
24 μs
23–25.5 μs
22–27 μs
Tp4
SOF 时间
48 μs
47–51 μs
46–63 μs
Tp5
EOF 时间
72 μs
70–76.5 μs
≥70 μs
数据按 MSB(最高位)优先 的顺序传输。三、帧结构
J1850 PWM 的完整帧结构如下:
+------+--------+--------+--------+-----+--------+------+
| SOF | Header | Data 1 | Data 2 | ... | CRC | EOD |
+------+--------+--------+--------+-----+--------+------+
48μs 1 byte 1 byte 1 byte 1 byte 200μs
高电平 MSB优先 MSB优先 MSB优先 MSB优先 低电平
帧组成部分:
SOF(Start of Frame) :帧起始标志,高电平持续 48 μs
Header(报文头) :1 字节,包含目标地址、源地址或消息类型等信息
Data(数据域) :1 至多个字节的有效载荷
CRC(循环冗余校验) :1 字节,对 Header + Data 进行计算
EOD(End of Data) :数据结束标志,低电平持续 200 μs
每帧最大长度为 101 个 PWM 位时间(不含 SOF、EOD 等符号),每帧至少包含 1 个数据字节和 1 个 CRC 字节。四、CRC 校验算法
J1850 协议使用 CRC-8 校验,生成多项式为:
多项式:x⁸ + x⁴ + x³ + x² + 1
十六进制:0x1D(二进制 1 0001 1101)
CRC 计算覆盖从 Header 到最后一个数据字节 的全部内容(不包括 SOF)。计算结果的 1's 补码(按位取反) 作为 CRC 字节附加在帧末尾。
CRC-8/J1850 计算实现
五、C++ 代码实现示例#include
#include/**
* @brief 计算 J1850 标准的 CRC-8 校验值
* @param data 待校验的数据指针
* @param len 数据长度(字节)
* @return uint8_t CRC 校验值(已取反)
*
* 多项式: x^8 + x^4 + x^3 + x^2 + 1 (0x1D)
* 初始值: 0xFF
* 最终异或: 0xFF (按位取反)
*/
uint8_t crc8_j1850(const uint8_t* data, size_t len) {
uint8_t crc = 0xFF; // 初始值
for (size_t i = 0; i < len; i++) {
crc ^= data[i];
for (int bit = 0; bit < 8; bit++) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x1D; // 异或多项式
} else {
crc <<= 1;
}
crc &= 0xFF;
}
}
return ~crc; // 按位取反(1's complement)
}
以下代码演示了 J1850 PWM 消息的封装、CRC 计算和位流生成。
5.1 消息结构定义
5.2 位流编码器#include
#include
#include/**
* @brief J1850 PWM 消息结构
*/
struct J1850PwmMessage {
uint8_t header; // 1 字节报文头
std::vector data; // 数据域(至少 1 字节)
/**
* @brief 获取完整帧的字节序列(Header + Data + CRC)
* @return std::vector 完整帧字节
*/
std::vector getFrameBytes() const {
std::vector frame;
frame.reserve(1 + data.size() + 1);
frame.push_back(header);
frame.insert(frame.end(), data.begin(), data.end());
// 计算并附加 CRC
uint8_t crc = crc8_j1850(frame.data(), frame.size());
frame.push_back(crc);
return frame;
}
};
5.3 发送器示例(硬件抽象层)#include
#include/**
* @brief J1850 PWM 位流编码器(Bit-banging 实现)
*
* 注意:实际硬件驱动需要接入具体的 GPIO 和定时器。
* 此处提供逻辑层实现,硬件层需根据平台适配。
*/
class J1850PwmEncoder {
public:
/**
* @brief 将字节序列编码为 PWM 位流时序
* @param bytes 待编码的字节序列
* @return std::vector 编码后的位序列(每个 bool 代表一个 bit)
*/
std::vector encodeToBits(const std::vector& bytes) {
std::vector bits;
bits.reserve(bytes.size() * 8);
for (uint8_t byte : bytes) {
// MSB 优先传输
for (int i = 7; i >= 0; i--) {
bits.push_back((byte >> i) & 0x01);
}
}
return bits;
}
/**
* @brief 将位序列转换为电平持续时间序列(微秒)
* @param bits 位序列
* @return std::vector 每个位的电平持续时间(μs),正数=高电平,负数=低电平
*/
std::vector bitsToLevelDurations(const std::vector& bits) {
std::vector durations;
durations.reserve(bits.size());
for (bool bit : bits) {
if (bit) {
// 逻辑 1:高电平 8μs,低电平 16μs
durations.push_back(8); // 高电平
durations.push_back(-16); // 低电平
} else {
// 逻辑 0:高电平 16μs,低电平 8μs
durations.push_back(16); // 高电平
durations.push_back(-8); // 低电平
}
}
return durations;
}
/**
* @brief 生成完整的帧时序(包含 SOF 和 EOD)
* @param msg J1850 PWM 消息
* @return std::vector 完整帧的电平持续时间序列
*/
std::vector generateFrameTiming(const J1850PwmMessage& msg) {
std::vector timing;
// 1. SOF:高电平 48 μs
timing.push_back(48);
// 2. 数据位(Header + Data + CRC)
std::vector frameBytes = msg.getFrameBytes();
std::vector bits = encodeToBits(frameBytes);
std::vector dataTiming = bitsToLevelDurations(bits);
timing.insert(timing.end(), dataTiming.begin(), dataTiming.end());
// 3. EOD:低电平 200 μs(部分实现使用 EOF 72μs)
timing.push_back(-200);
return timing;
}
};
5.4 使用示例/**
* @brief 硬件抽象层接口(需根据实际平台实现)
*/
class HardwareAbstractionLayer {
public:
virtual void setBusHigh() = 0; // 设置 Bus+ 高,Bus- 低
virtual void setBusLow() = 0; // 设置 Bus+ 低,Bus- 高
virtual void delayMicroseconds(int us) = 0; // 微秒级延时
virtual ~HardwareAbstractionLayer() = default;
};/**
* @brief J1850 PWM 发送器
*/
class J1850PwmTransmitter {
private:
HardwareAbstractionLayer* hal;
J1850PwmEncoder encoder;
public:
explicit J1850PwmTransmitter(HardwareAbstractionLayer* h) : hal(h) {}
/**
* @brief 发送一条 J1850 PWM 消息
* @param msg 待发送的消息
* @return bool 发送成功返回 true
*/
bool sendMessage(const J1850PwmMessage& msg) {
std::vector timing = encoder.generateFrameTiming(msg);
for (int duration : timing) {
if (duration > 0) {
hal->setBusHigh(); // 高电平
} else {
hal->setBusLow(); // 低电平
}
hal->delayMicroseconds(duration > 0 ? duration : -duration);
}
// 帧间隔(IFS):至少 96 μs
hal->setBusLow();
hal->delayMicroseconds(96);
return true;
}
};
#include
/**
* @brief 示例:发送 OBD-II 模式 01 请求(读取当前数据)
*
* 典型 Ford SCP 请求:
* Header: 0x68 (物理寻址,目标地址)
* Data: 0x6A 0xF1 0x01 0x00 (OBD 模式 01 PID 00)
*/
void exampleSendObdRequest() {
// 1. 构造消息
J1850PwmMessage msg;
msg.header = 0x68;
msg.data = {0x6A, 0xF1, 0x01, 0x00};
// 2. 获取完整帧字节(含 CRC)
std::vector frame = msg.getFrameBytes();
std::cout << "Frame bytes (hex): ";
for (uint8_t b : frame) {
printf("%02X ", b);
}
std::cout << std::endl;
// 3. 生成位时序(示意)
J1850PwmEncoder encoder;
std::vector bits = encoder.encodeToBits(frame);
std::cout << "Total bits: " << bits.size() << std::endl;
std::cout << "Frame length: " << (bits.size() * 24) << " μs (excluding SOF/EOD)" << std::endl;
// 4. 实际发送需要硬件抽象层实现
// HardwareAbstractionLayer* hal = new YourPlatformHAL();
// J1850PwmTransmitter transmitter(hal);
// transmitter.sendMessage(msg);
}int main() {
exampleSendObdRequest();
return 0;
}
输出示例:
Frame bytes (hex): 68 6A F1 01 00 4F
Total bits: 48
Frame length: 1152 μs (excluding SOF/EOD)
六、实战注意事项 6.1 硬件连接J1850 PWM 使用 OBD-II 接口的 Pin 2(Bus+) 和 Pin 10(Bus-)
需要 5V 电平的差分收发器(如 MC33389 或 MSM6636B 等专用控制器)
可直接使用 Arduino 等平台进行 Bit-banging 实现
J1850 使用 CSMA/CR(载波侦听多路访问/冲突解决) 机制。在发送每个位之前,节点需检测总线状态:
高电平为 主导(dominant) 状态
多个节点同时发送时,发送 1 的节点会退出发送(因为 1 的高电平持续时间更短)
读取故障码(DTC) :OBD 模式 03 / 07
读取实时数据 :OBD 模式 01
清除故障码 :OBD 模式 04
Ford SCP 协议的诊断通信
J1850 PWM 和 J1850 VPW 物理层不兼容。连接车辆前需确认:
Ford / Mazda → J1850 PWM(Pin 2 & 10)
GM(通用) → J1850 VPW(Pin 2 单线)
Chrysler(克莱斯勒) → J1850 VPW(不同帧格式)
J1850 PWM 是汽车诊断通信史上的重要协议,虽然在现代车辆中已逐渐被 CAN 总线取代,但在老款 Ford、Mazda 等车型的维修诊断中仍有不可替代的地位。理解其物理层特性、帧结构和 CRC 算法,对于开发 OBD-II 诊断工具或进行老车电子系统维护具有实际意义。
上述 C++ 代码提供了从消息封装、CRC 计算到位流生成和发送的完整逻辑,可根据具体硬件平台适配底层 GPIO 和定时器驱动。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.