网易首页 > 网易号 > 正文 申请入驻

UDS 诊断协议:数据传输机制与 C++ 实现

0
分享至


UDS 诊断协议:数据传输机制与 C++ 实现 结论

UDS(统一诊断服务)运行在 CAN 总线之上时,单条诊断消息最大可达 4095 字节,而经典 CAN 数据场仅 8 字节。ISO 15765-2(ISO-TP)通过单帧(SF) 与多帧(FF/CF/FC) 两套机制,在有限带宽上实现了诊断数据的分段与可靠重组。单帧适用于长度 ≤ 7 字节的消息,多帧则通过“首帧宣告长度 → 流控帧协商参数 → 连续帧逐块传输”的三方握手完成长消息传输。以下从协议机制到 C++ 工程实现展开说明。

一、协议栈位置与核心概念

UDS 遵循 OSI 分层模型,其中与 CAN 传输直接相关的两层是:

OSI 层

协议

职责

应用层

ISO 14229 (UDS)

诊断服务(如 0x22 读数据、0x2E 写数据)

传输层

ISO 15765-2 (ISO-TP)

分段、重组、流控

数据链路层

CAN 2.0A/B

8 字节数据场,11/29 位标识符

UDS 本质上是一系列诊断服务,每种服务有独立的服务标识符(SID),如 0x10 诊断会话控制、0x22 读数据、0x2E 写数据。肯定响应的 SID 为请求 SID + 0x40(例如 0x22 → 0x62),否定响应固定为 0x7F。

传输层协议数据单元(PDU)通过 CAN 数据场的第一个字节的高半字节(N_PCI) 区分帧类型:0x0 = 单帧,0x1 = 首帧,0x2 = 连续帧,0x3 = 流控帧。

二、单帧传输(Single Frame)

当 UDS 请求或响应的有效载荷总长度不超过 7 字节时,使用单帧一次性完成传输。

格式:

字节 0:SF 标识(高 4 位 = 0x0)+ 有效数据长度(低 4 位,1~7)
字节 1~7:应用层数据

示例: 请求读取 DID 0xF186(当前诊断会话数据标识符),UDS 报文为 22 F1 86(共 3 字节),构造出的 CAN 数据场为:

03 22 F1 86 00 00 00 00

其中 03 表示单帧携带 3 字节有效数据,22 F1 86 为 UDS 服务请求。

三、多帧传输机制

当有效载荷超过 7 字节时,进入多帧传输流程,使用三种帧类型协同完成。

3.1 首帧(First Frame, FF)

发送方先发出首帧,宣告总数据长度(12 位值,最大 4095),并携带前 6 字节数据。

格式:

字节 0:FF 标识(高 4 位 = 0x1)+ 总长度高 4 位
字节 1:总长度低 8 位
字节 2~7:前 6 字节应用层数据
3.2 流控帧(Flow Control, FC)

接收方收到 FF 后,若缓冲区允许,回复流控帧告知发送方如何继续。

格式:

字节 0:FC 标识(高 4 位 = 0x3)+ 流状态(FS,低 4 位)
字节 1:块大小(BS)
字节 2:最小间隔时间(STmin)
  • FS :0 = 继续发送(CTS),1 = 等待(WT),2 = 溢出/中止(OVFLW)

  • BS :发送方在等待下一个 FC 之前可连续发送的 CF 数量。0 表示不再需要 FC,一次发完

  • STmin :连续帧之间的最小时间间隔,控制发送节奏

3.3 连续帧(Consecutive Frame, CF)

发送方收到 CTS 流控帧后,开始发送连续帧,每帧携带 7 字节数据。

格式:

字节 0:CF 标识(高 4 位 = 0x2)+ 序列号 SN(低 4 位,1~15 循环)
字节 1~7:后续数据

序列号从 1 开始递增,每发一帧加 1,超过 15 后回绕到 0。接收方通过 SN 检测丢帧。每发送完一个 BS 大小的块后,发送方暂停等待接收方发来下一个 FC 帧。

四、C++ 代码实现

以下实现基于经典 CAN(8 字节数据场),提供发送方的分段逻辑与接收方的重组逻辑。

4.1 发送方:ISO-TP 分段器

#include  
          
#include
#include
#include

class IsoTpSender {
public:
using CanTxCallback = std::function& data)>;

void SetTxCallback(CanTxCallback cb) { txCb_ = std::move(cb); }

// 发送完整的 UDS 报文(自动选择 SF 或 MF)
void SendUdsMessage(uint32_t canId, const std::vector& udsPayload) {
if (udsPayload.empty()) return;

if (udsPayload.size() <= 7) {
SendSingleFrame(canId, udsPayload);
} else {
SendFirstFrame(canId, udsPayload);
}
}

private:
CanTxCallback txCb_;

void SendSingleFrame(uint32_t canId, const std::vector& payload) {
std::vector frame(8, 0x00);
frame[0] = static_cast(payload.size() & 0x0F); // PCI: 0x0N
std::copy(payload.begin(), payload.end(), frame.begin() + 1);
txCb_(canId, frame);
}

void SendFirstFrame(uint32_t canId, const std::vector& payload) {
uint16_t totalLen = static_cast(payload.size());
std::vector ff(8, 0x00);
// PCI 高4位=0x1, 低4位为长度高4位
ff[0] = 0x10 | static_cast((totalLen >> 8) & 0x0F);
ff[1] = static_cast(totalLen & 0xFF);
// 首帧携带前 6 字节数据
size_t copyLen = std::min(6, payload.size());
std::copy(payload.begin(), payload.begin() + copyLen, ff.begin() + 2);
txCb_(canId, ff);

// 实际工程中,此处应等待流控帧(FC)后再继续发送 CF。
// 以下为简化演示:假设接收方已回复 CTS(BS=0, STmin=0)
SendConsecutiveFrames(canId, payload, copyLen, 1);
}

void SendConsecutiveFrames(uint32_t canId, const std::vector& payload,
size_t offset, uint8_t startSn) {
uint8_t sn = startSn;
while (offset < payload.size()) {
std::vector cf(8, 0x00);
cf[0] = 0x20 | (sn & 0x0F); // PCI: 0x2N
size_t chunk = std::min(7, payload.size() - offset);
std::copy(payload.begin() + offset, payload.begin() + offset + chunk, cf.begin() + 1);
txCb_(canId, cf);
offset += chunk;
sn = (sn + 1) & 0x0F; // 1~15 循环,跳过 0 亦可,此处简化为回绕到 0
}
}
};
4.2 接收方:ISO-TP 重组器

#include  
          
#include
#include
#include

class IsoTpReceiver {
public:
using UdsIndicationCallback = std::function& udsPayload)>;
using CanTxCallback = std::function& data)>;

void SetIndicationCallback(UdsIndicationCallback cb) { indCb_ = std::move(cb); }
void SetTxCallback(CanTxCallback cb) { txCb_ = std::move(cb); }

// 处理从 CAN 总线收到的原始数据帧
void OnCanFrameReceived(uint32_t canId, const std::vector& canData) {
if (canData.size() < 1) return;

uint8_t pciType = (canData[0] >> 4) & 0x0F;

switch (pciType) {
case 0x0: HandleSingleFrame(canId, canData); break;
case 0x1: HandleFirstFrame(canId, canData); break;
case 0x2: HandleConsecutiveFrame(canId, canData); break;
case 0x3: HandleFlowControl(canId, canData); break; // 发送方角色时使用
default: break;
}
}

private:
UdsIndicationCallback indCb_;
CanTxCallback txCb_;

// 多帧接收状态
std::vector rxBuffer_;
uint16_t expectedTotalLen_ = 0;
uint8_t lastSn_ = 0;
bool receivingMultiFrame_ = false;

void HandleSingleFrame(uint32_t canId, const std::vector& canData) {
uint8_t dataLen = canData[0] & 0x0F;
if (dataLen == 0 || dataLen > 7) return;
std::vector payload(canData.begin() + 1, canData.begin() + 1 + dataLen);
if (indCb_) indCb_(payload);
}

void HandleFirstFrame(uint32_t canId, const std::vector& canData) {
expectedTotalLen_ = ((canData[0] & 0x0F) << 8) | canData[1];
rxBuffer_.clear();
rxBuffer_.reserve(expectedTotalLen_);
// FF 携带前 6 字节数据(有效载荷部分,需考虑实际长度)
size_t copyLen = std::min(6, expectedTotalLen_);
rxBuffer_.insert(rxBuffer_.end(), canData.begin() + 2, canData.begin() + 2 + copyLen);
lastSn_ = 0;
receivingMultiFrame_ = true;

// 回复流控帧:CTS, BS=0(一次发完), STmin=0
SendFlowControl(canId, 0x00, 0x00, 0x00);
}

void HandleConsecutiveFrame(uint32_t canId, const std::vector& canData) {
if (!receivingMultiFrame_) return;

uint8_t sn = canData[0] & 0x0F;
// 检查序列号连续性(简化:期望 sn == lastSn_ + 1,回绕到 0 后继续)
uint8_t expectedSn = (lastSn_ + 1) & 0x0F;
if (sn != expectedSn && !(lastSn_ == 15 && sn == 0)) {
// 序列号异常,重置接收
receivingMultiFrame_ = false;
return;
}

size_t remaining = expectedTotalLen_ - rxBuffer_.size();
size_t copyLen = std::min(7, remaining);
rxBuffer_.insert(rxBuffer_.end(), canData.begin() + 1, canData.begin() + 1 + copyLen);
lastSn_ = sn;

if (rxBuffer_.size() >= expectedTotalLen_) {
receivingMultiFrame_ = false;
if (indCb_) indCb_(rxBuffer_);
}
}

void SendFlowControl(uint32_t canId, uint8_t fs, uint8_t bs, uint8_t stmin) {
std::vector fc(8, 0x00);
fc[0] = 0x30 | (fs & 0x0F); // PCI: 0x3N
fc[1] = bs;
fc[2] = stmin;
if (txCb_) txCb_(canId, fc);
}
};

使用示例:

int main() {
IsoTpSender sender;
IsoTpReceiver receiver;

// 模拟 CAN 收发(实际应绑定 SocketCAN 或 CAN 驱动)
receiver.SetTxCallback([](uint32_t id, const std::vector& d) {
// 接收方发出的 FC 帧通过此回调发出
});
sender.SetTxCallback([](uint32_t id, const std::vector& d) {
// 实际工程中,这里调用 CAN 驱动发送 d 到总线
// 在联调时,可将此帧直接喂给 receiver.OnCanFrameReceived
});

receiver.SetIndicationCallback([](const std::vector& uds) {
// 处理完整的 UDS 报文
// 例如 0x62 F1 86 ... 表示对 0x22 F1 86 的肯定响应
});

// 发送一个超过 7 字节的 UDS 请求示例:0x2E F1 90 + 长数据
std::vector longRequest = {0x2E, 0xF1, 0x90, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
sender.SendUdsMessage(0x7DF, longRequest);

return 0;
}
五、关键注意事项

流控帧的时序约束:发送方在发出首帧后,必须等待接收方的流控帧才能继续发送连续帧。接收方未回复 FC 或回复等待(WT)帧时,发送方不得发送 CF。实际工程中需实现超时重传机制。

序列号检测:连续帧的 SN 从 1 开始,每帧递增,超过 15 后回绕到 0。接收方通过 SN 是否连续来判断是否有丢帧。若 SN 不连续,应终止当前重组并重置状态。

CAN FD 的差异:若使用 CAN FD(64 字节数据场),单帧可携带的载荷从 7 字节提升到 62 字节,多帧的帧结构需相应调整(首帧携带的数据量、连续帧的 chunk 大小均变化)。上述代码针对经典 CAN 8 字节场景,移植到 CAN FD 时需修改 copyLen 等常量。

SocketCAN 的 ISO-TP 支持:Linux 内核已内置 ISO-TP 协议栈,用户空间可通过 socket(PF_CAN, SOCK_DGRAM, CAN_ISOTP) 创建套接字,内核自动处理分段与重组,应用层只需读写完整数据场。但在嵌入式 ECU 或需精细控制时序的场景中,手动实现 ISO-TP 分段仍是常见做法。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

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.

相关推荐
热点推荐
知名大学原书记、校长,主动向组织交代问题,被查!2026年10月10日,他仍以该校“党委书记”身份出席公开活动

知名大学原书记、校长,主动向组织交代问题,被查!2026年10月10日,他仍以该校“党委书记”身份出席公开活动

双一流高校
2026-10-24 03:19:47
画面不堪入目,地下人奶交易乱象,成年人躺进怀里吃奶的灰色黑产

画面不堪入目,地下人奶交易乱象,成年人躺进怀里吃奶的灰色黑产

易玄
2026-09-12 10:24:46
突发!林秀成被批捕!

突发!林秀成被批捕!

中国半导体论坛
2026-10-24 00:45:22
一句“盲猜”引爆医疗圈!医学生李子祺上综艺翻车,网传欺瞒导师录节目,网友:导师的天塌了,用啥填窟窿呢

一句“盲猜”引爆医疗圈!医学生李子祺上综艺翻车,网传欺瞒导师录节目,网友:导师的天塌了,用啥填窟窿呢

火山詩话
2026-10-24 06:42:43
歌手毛不易出席20多年好友婚礼致辞引争议:“虽然不是我的妻子”,但从校服到婚纱,是我生活中交集最多的人

歌手毛不易出席20多年好友婚礼致辞引争议:“虽然不是我的妻子”,但从校服到婚纱,是我生活中交集最多的人

台州交通广播
2026-10-23 17:49:45
王志军任湖南省委书记

王志军任湖南省委书记

界面新闻
2026-10-24 09:38:17
“卤肉可以卖,米饭不允许”,贵阳一学校美食街卤肉饭店被禁售米饭,校方回应:为避免和食堂形成竞争,如果商户要退租,可退租金押金

“卤肉可以卖,米饭不允许”,贵阳一学校美食街卤肉饭店被禁售米饭,校方回应:为避免和食堂形成竞争,如果商户要退租,可退租金押金

潇湘晨报
2026-10-23 18:13:09
五常总算表态了!随着中日摩擦不断加剧,五个常任理事国不再保持沉默,纷纷亮出立场:这一回各自到底站哪边,结果出乎外界预料!

五常总算表态了!随着中日摩擦不断加剧,五个常任理事国不再保持沉默,纷纷亮出立场:这一回各自到底站哪边,结果出乎外界预料!

谈史论今1
2026-10-22 19:57:16
“在星巴克开会1小时被提醒点单5次”冲上热搜,星巴克回应:我们倡导友善共享,相互尊重,而非冲突,会结合门店实际情况灵活处理

“在星巴克开会1小时被提醒点单5次”冲上热搜,星巴克回应:我们倡导友善共享,相互尊重,而非冲突,会结合门店实际情况灵活处理

芒果都市
2026-10-23 11:01:40
72岁李雪健已“听不见,也说不了话”,一段演绎看哭众网友!此前因鼻咽癌放疗造成听觉神经、唾液腺和声带损伤

72岁李雪健已“听不见,也说不了话”,一段演绎看哭众网友!此前因鼻咽癌放疗造成听觉神经、唾液腺和声带损伤

都市快报橙柿互动
2026-10-23 22:40:48
世锦赛:中美俄日瓜分奖牌,美国首金!力压奥运冠军,柯沁沁夺铜

世锦赛:中美俄日瓜分奖牌,美国首金!力压奥运冠军,柯沁沁夺铜

求球不落谛
2026-10-24 07:32:23
俄副总理自曝:两年半赴俄看病中国人涨32%,他们到底去治啥?

俄副总理自曝:两年半赴俄看病中国人涨32%,他们到底去治啥?

古史青云啊
2026-10-23 09:43:02
我是一根柱子,在“地球最强SUV”的撞击中,活了下来且只是轻伤

我是一根柱子,在“地球最强SUV”的撞击中,活了下来且只是轻伤

News脑洞
2026-10-23 10:51:48
日军投降前一夜有多疯狂?一个日本女军医:日军用刀劈开了一个人

日军投降前一夜有多疯狂?一个日本女军医:日军用刀劈开了一个人

小豫讲故事
2026-10-24 06:00:12
孙怡说李小冉“你这个年纪想找头婚的不容易”,李小冉笑称“这怎么就不能短剧照进现实呢?霸道总裁爱上我”

孙怡说李小冉“你这个年纪想找头婚的不容易”,李小冉笑称“这怎么就不能短剧照进现实呢?霸道总裁爱上我”

韩小娱
2026-10-23 20:48:57
中共自然资源部党组关于何广顺、郭兆敏、张绍丽等同志职务任免的通知

中共自然资源部党组关于何广顺、郭兆敏、张绍丽等同志职务任免的通知

海洋知圈
2026-10-24 01:07:56
巴萨官方祝贺梅西获奖:你永远是巴萨是一员,是我们骄傲的源泉

巴萨官方祝贺梅西获奖:你永远是巴萨是一员,是我们骄傲的源泉

懂球帝
2026-10-24 02:43:16
贝克汉姆15岁女儿哭诉在校被禁化妆?爸妈砸300万给她美妆创业没水花,网友:读书吧你!

贝克汉姆15岁女儿哭诉在校被禁化妆?爸妈砸300万给她美妆创业没水花,网友:读书吧你!

英国报姐
2026-10-24 00:58:45
下午13点,孙心然冲决赛,郑钦文“闹”大了:联合国官方发文盛赞

下午13点,孙心然冲决赛,郑钦文“闹”大了:联合国官方发文盛赞

大秦壁虎白话体育
2026-10-24 00:03:32
苍井空公开承认南京大屠杀:我在上学时学到过,一切的根源都在于战争

苍井空公开承认南京大屠杀:我在上学时学到过,一切的根源都在于战争

不掉线电波
2026-10-21 12:22:08
2026-10-24 09:59:00
新能源自动驾驶 incentive-icons
新能源自动驾驶
专注于半导体行业资讯
1024文章数 348关注度
往期回顾 全部

科技要闻

写程序、做3D,新Kimi还有多少本事?

头条要闻

牛弹琴:难得的一个好消息 梅拉尼娅促成俄乌局部停火

头条要闻

牛弹琴:难得的一个好消息 梅拉尼娅促成俄乌局部停火

体育要闻

没人看好的加图索,打出队史最佳开局

娱乐要闻

黎纪君晒与刘恺威合照 恋情后首次同框

财经要闻

叫"保障"不叫"保险":租车平台的文字游戏

汽车要闻

设计换新/智舱升级 2027款博越限时价7.89万起

态度原创

游戏
数码
艺术
房产
公开课

四支中国队全败!《英雄联盟》总决赛最差开局

数码要闻

最高涨24%!AMD Zen 6同频性能首曝:4GHz媲美上代5.5GHz

艺术要闻

美到窒息!国外人像摄影刷屏,真是模特太美,还是摄影师开挂?

房产要闻

百亩大盘又 +1!攒量超 200 万㎡!江东,要燃起来了!

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版