![]()
UDS 在 LIN 协议上的实现(UDSonLIN) 核心结论
UDSonLIN 是 ISO 14229-7 定义的标准,将 UDS 诊断服务映射到 LIN 总线上,实现主节点通过 LIN 网络对从节点进行标准化诊断和固件升级。 诊断请求通过 LIN 的 ID=0x3C 帧发送,响应通过 ID=0x3D 帧返回,使用 NAD(节点地址)标识目标从节点,传输层基于 ISO 15765-2 的帧格式(单帧/多帧)适配 LIN 的 8 字节数据场限制。
一、UDSonLIN 的协议架构 1.1 与 UDSonCAN 的关键差异
特性
UDSonCAN
UDSonLIN
数据链路
CAN 帧(最多 8 字节)
LIN 帧(固定 8 字节数据场)
诊断请求帧
标准 CAN ID
LIN ID = 0x3C(主请求帧)
诊断响应帧
标准 CAN ID
LIN ID = 0x3D(从响应帧)
节点寻址
通过 CAN ID 隐含
通过 NAD(首字节)显式指定
物理层速率
500 kbps ~ 1 Mbps
10 ~ 20 kbps
诊断通信始终由主节点发起:主节点通过 ID=0x3C 发送请求,目标从节点通过 ID=0x3D 返回响应。主节点本身不直接响应诊断请求,而是充当诊断仪与 LIN 从节点之间的网关。
1.2 LIN 诊断帧的数据结构
使用传输层协议时,LIN 数据场的 8 个字节按如下格式排列:
字节 0: NAD (Node Address) — 目标从节点地址,0x7E 为功能寻址
字节 1: PCI (Protocol Control Information) — 高4位帧类型,低4位数据长度
字节 2~6: 数据 (SID + 参数 或 RSID + 参数)
字节 7: 填充 0xFF(未使用字节)
PCI 高 4 位定义帧类型:
0x0X:单帧(Single Frame),数据长度 1~6 字节0x1X:首帧(First Frame),后续有连续帧0x2X:连续帧(Consecutive Frame)
以下代码模拟主节点通过 LIN 总线发送 UDS 诊断请求的完整流程,涵盖单帧传输和多帧传输两种场景。
使用示例// udsonlin_demo.cpp
#include
#include
#include
#include
// ---------- LIN 诊断帧常量 ----------
constexpr uint8_t LIN_DIAG_REQUEST_ID = 0x3C; // 主请求帧 ID
constexpr uint8_t LIN_DIAG_RESPONSE_ID = 0x3D; // 从响应帧 ID
constexpr uint8_t NAD_FUNCTIONAL = 0x7E; // 功能寻址
constexpr uint8_t PAD_BYTE = 0xFF; // 填充字节
// ---------- UDS 服务 ID ----------
enum UdsSid : uint8_t {
SID_DIAGNOSTIC_SESSION_CONTROL = 0x10,
SID_ECU_RESET = 0x11,
SID_READ_DATA_BY_IDENTIFIER = 0x22,
SID_SECURITY_ACCESS = 0x27,
SID_WRITE_DATA_BY_IDENTIFIER = 0x2E,
SID_ROUTINE_CONTROL = 0x31,
SID_REQUEST_DOWNLOAD = 0x34,
SID_TRANSFER_DATA = 0x36,
SID_REQUEST_TRANSFER_EXIT = 0x37
};
// ---------- PCI 帧类型 ----------
enum PciType : uint8_t {
PCI_SINGLE_FRAME = 0x00,
PCI_FIRST_FRAME = 0x10,
PCI_CONSEC_FRAME = 0x20
};
// ---------- LIN 帧结构(数据场固定 8 字节)----------
struct LinFrame {
uint8_t id; // LIN ID (0x3C / 0x3D)
uint8_t data[8];
};
// 构建单帧诊断请求
// 单帧数据最大 6 字节(NAD + PCI + 最多 6 字节 UDS 负载)
LinFrame buildSingleFrameRequest(uint8_t nad, const std::vector& udsPayload) {
LinFrame frame;
frame.id = LIN_DIAG_REQUEST_ID;
// 字节 0: NAD
frame.data[0] = nad;
// 字节 1: PCI — 单帧,低4位为数据长度
frame.data[1] = PCI_SINGLE_FRAME | (udsPayload.size() & 0x0F);
// 字节 2~: UDS 负载
for (size_t i = 0; i < udsPayload.size() && i < 6; ++i) {
frame.data[2 + i] = udsPayload[i];
}
// 填充 0xFF
for (size_t i = 2 + udsPayload.size(); i < 8; ++i) {
frame.data[i] = PAD_BYTE;
}
return frame;
}
// 构建多帧诊断请求(首帧 + 连续帧序列)
std::vector buildMultiFrameRequest(uint8_t nad,
const std::vector& udsPayload) {
std::vector frames;
// --- 首帧 (FF) ---
LinFrame ff;
ff.id = LIN_DIAG_REQUEST_ID;
ff.data[0] = nad;
// PCI 首帧: 高4位 0x1,低4位为总长度高 4 位
uint16_t totalLen = static_cast(udsPayload.size());
ff.data[1] = PCI_FIRST_FRAME | ((totalLen >> 8) & 0x0F);
ff.data[2] = totalLen & 0xFF; // 长度低 8 位
// 首帧数据最多 5 字节 UDS 负载(字节 3~7)
size_t offset = 0;
for (size_t i = 3; i < 8 && offset < udsPayload.size(); ++i, ++offset) {
ff.data[i] = udsPayload[offset];
}
frames.push_back(ff);
// --- 连续帧 (CF) ---
uint8_t seqNum = 1; // 连续帧序列号从 1 开始
while (offset < udsPayload.size()) {
LinFrame cf;
cf.id = LIN_DIAG_REQUEST_ID;
cf.data[0] = nad;
cf.data[1] = PCI_CONSEC_FRAME | (seqNum & 0x0F);
// 每帧最多 6 字节 UDS 负载(字节 2~7)
for (size_t i = 2; i < 8 && offset < udsPayload.size(); ++i, ++offset) {
cf.data[i] = udsPayload[offset];
}
// 填充
for (size_t i = 2 + (udsPayload.size() % 6 == 0 ? 6 : udsPayload.size() % 6); i < 8; ++i) {
cf.data[i] = PAD_BYTE;
}
frames.push_back(cf);
seqNum = (seqNum + 1) & 0x0F;
}
return frames;
}
// 解析单帧诊断响应
// 返回 RSID 及数据;若为否定响应(0x7F),返回 NRC
bool parseResponse(const LinFrame& frame, uint8_t& outRsid,
std::vector& outData, uint8_t& outNrc) {
if (frame.id != LIN_DIAG_RESPONSE_ID) return false;
// 字节 0: NAD(回显请求的 NAD)
// 字节 1: PCI
uint8_t pciType = frame.data[1] >> 4;
uint8_t dataLen = frame.data[1] & 0x0F;
if (pciType != PCI_SINGLE_FRAME) return false; // 简化:仅处理单帧响应
// 字节 2: RSID 或 0x7F
if (frame.data[2] == 0x7F) {
// 否定响应: [0x7F] [原 SID] [NRC]
outNrc = frame.data[4];
outRsid = 0x7F;
outData.clear();
return true;
}
outRsid = frame.data[2];
outData.assign(frame.data + 3, frame.data + 2 + dataLen);
return true;
}// 打印 LIN 帧用于调试
void printFrame(const LinFrame& f, const std::string& tag) {
std::cout << tag << " [ID=0x" << std::hex << (int)f.id << "]: ";
for (int i = 0; i < 8; ++i) {
std::cout << std::setw(2) << std::setfill('0')
<< (int)f.data[i] << " ";
}
std::cout << std::dec << std::endl;
}
int main() {
// --- 示例 1: 诊断会话控制(单帧)---
// 请求编程会话: SID=0x10, Sub=0x02
// 预期 LIN 数据: NAD=0x83, PCI=0x02, SID=0x10, Sub=0x02
{
uint8_t nad = 0x83;
std::vector payload = {
SID_DIAGNOSTIC_SESSION_CONTROL, 0x02
};
LinFrame req = buildSingleFrameRequest(nad, payload);
printFrame(req, "诊断会话请求");
// 模拟从节点响应: RSID=0x50, 会话参数
LinFrame resp;
resp.id = LIN_DIAG_RESPONSE_ID;
resp.data[0] = nad;
resp.data[1] = 0x02; // 单帧,2字节有效数据
resp.data[2] = 0x50; // RSID = SID + 0x40
resp.data[3] = 0x02; // 已进入编程会话
for (int i = 4; i < 8; ++i) resp.data[i] = PAD_BYTE;
printFrame(resp, "诊断会话响应");
uint8_t rsid, nrc;
std::vector data;
if (parseResponse(resp, rsid, data, nrc)) {
std::cout << "RSID: 0x" << std::hex << (int)rsid << std::dec << std::endl;
}
}
// --- 示例 2: 写入数据(多帧,超过 6 字节)---
// WriteDataByIdentifier: SID=0x2E, DID=0xF190, 写入 VIN 码 17 字节
{
uint8_t nad = 0x83;
std::vector payload = { SID_WRITE_DATA_BY_IDENTIFIER };
payload.push_back(0xF1); // DID 高字节
payload.push_back(0x90); // DID 低字节
// VIN 码 17 字节
const char* vin = "1HGCM82633A004352";
for (int i = 0; i < 17; ++i) payload.push_back(vin[i]);
std::cout << "\nUDS 负载总长度: " << payload.size() << " 字节" << std::endl;
auto frames = buildMultiFrameRequest(nad, payload);
for (size_t i = 0; i < frames.size(); ++i) {
std::string tag = (i == 0) ? "首帧" : ("连续帧 " + std::to_string(i));
printFrame(frames[i], tag);
}
}
return 0;
}
代码输出解释示例 1 输出:
诊断会话请求 [ID=0x3c]: 83 02 10 02 ff ff ff ff
诊断会话响应 [ID=0x3d]: 83 02 50 02 ff ff ff ff
RSID: 0x50
数据帧 83 02 10 02 的含义:NAD=0x83(目标从节点),PCI=0x02(单帧,2 字节有效数据),SID=0x10(诊断会话控制),Sub=0x02(编程会话)。
示例 2 输出: 17 字节 VIN + SID + DID = 20 字节负载,超过单帧 6 字节上限,因此拆分为首帧 + 3 个连续帧。首帧的 PCI 格式为 0x1X 0xYY,其中 XXY 表示总长度(20 = 0x014),后续连续帧的 PCI 为 0x21、0x22、0x23。
三、传输层的关键设计考量 3.1 超时处理
LIN 诊断通信中,主节点发送请求后需等待从节点响应。如果在 P2Server 超时时间内未收到响应,主节点会重发请求(使用 ID=0x3C),直到超时计数器耗尽。功能寻址(NAD=0x7E)下,多个从节点可能同时响应,主节点需要处理仲裁或冲突检测。
3.2 诊断类别
ISO 14229-7 定义了三种诊断类别:
Class I :最小诊断,仅支持基础服务(如读取 DTC)
Class II :中等诊断,支持会话控制和数据读写
Class III :完整诊断,支持编程/刷写
从节点的诊断能力通过 LIN 节点的配置服务(SID B016~B816)声明。
四、工程实践中的注意事项
NAD 分配规则:NAD 范围 0x01~0x7F,其中 0x7E 为功能寻址地址,0x80~0xFF 保留给特定应用或历史兼容性使用。从节点的 NAD 通常在 LIN 描述文件(LDF)中配置。
Bootloader 场景:OTA 升级时,诊断流程通常为:进入扩展会话(0x10 0x03)→ 安全访问(0x27)→ 进入编程会话(0x10 0x02)→ 请求下载(0x34)→ 传输数据(0x36)→ 退出传输(0x37)→ ECU 复位(0x11)。LIN 总线上实测传输速率约 3.7 包/秒,128KB 固件升级约需 15 分钟。
实时性限制:LIN 的低波特率(通常 19.2 kbps)意味着 UDSonLIN 不适合对时间敏感的交互。诊断通信必须与其他 LIN 调度帧共享带宽,主节点需要合理分配调度表中的诊断时隙。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
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.