打开网易新闻 查看精彩图片
这是一份关于UDS协议的详细讲解与C++代码示例,包含协议核心机制说明及一个完整的可编译模拟实现,帮助您直观理解诊断通信流程。
// ============================================================================
// uds_demo.cpp —— UDS (ISO 14229-1) + ISO-TP (ISO 15765-2) 教学实现
// 单文件可直接编译: g++ -std=c++17 -O2 uds_demo.cpp -o uds_demo
// ============================================================================
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace uds {
using Byte = std::uint8_t;
using Bytes = std::vector ;
// ============================================================================
// 1. 协议常量
// ============================================================================
enum class Service : Byte {
DiagnosticSessionControl = 0x10,
ECUReset = 0x11,
ClearDiagnosticInfo = 0x14,
ReadDTCInformation = 0x19,
ReadDataByIdentifier = 0x22,
SecurityAccess = 0x27,
CommunicationControl = 0x28,
WriteDataByIdentifier = 0x2E,
RoutineControl = 0x31,
TesterPresent = 0x3E,
};
constexpr Byte kNegativeResponseSid = 0x7F;
enum class Nrc : Byte {
GeneralReject = 0x10,
ServiceNotSupported = 0x11,
SubFunctionNotSupported = 0x12,
IncorrectMessageLength = 0x13,
ConditionsNotCorrect = 0x22,
RequestSequenceError = 0x24,
RequestOutOfRange = 0x31,
SecurityAccessDenied = 0x33,
InvalidKey = 0x35,
ExceedNumberOfAttempts = 0x36,
RequiredTimeDelayNotExpired = 0x37,
ResponsePending = 0x78,
SubFunctionNotSupportedInSession = 0x7E,
ServiceNotSupportedInSession = 0x7F,
};
enum class Session : Byte {
Default = 0x01,
Programming = 0x02,
Extended = 0x03,
};
// 正 / 负响应构造
inline Bytes positiveResponse(Byte sid, const Bytes& data = {}) {
Bytes r;
r.reserve(1 + data.size());
r.push_back(static_cast (sid + 0x40)); // 正响应 SID = 请求 SID + 0x40
r.insert(r.end(), data.begin(), data.end());
return r;
}
inline Bytes negativeResponse(Byte sid, Nrc nrc) {
return Bytes{ kNegativeResponseSid, sid, static_cast (nrc) };
}
// 安全访问算法(⚠ 真实 OEM 为保密算法,此处仅用于演示握手流程)
inline std::uint16_t computeKey(std::uint16_t seed) {
std::uint32_t k = (static_cast(seed) << 3) ^ 0x5A5Au;
return static_cast(k & 0xFFFFu);
}
// ============================================================================
// 2. ISO 15765-2 (ISO-TP):把 UDS 报文切分 / 重组为 8 字节 CAN 帧
// ============================================================================
class IsoTp {
public:
static constexpr std::size_t CAN_DL = 8; // 经典 CAN 数据段
static constexpr std::size_t SF_MAX = CAN_DL - 1; // 单帧最多 7 字节
static constexpr std::size_t FF_MAX = CAN_DL - 2; // 首帧数据 6 字节
static constexpr std::size_t CF_MAX = CAN_DL - 1; // 连续帧数据 7 字节
// ---- 发送侧:UDS 报文 -> CAN 帧序列 ----
static std::vector segment(const Bytes& payload) {
std::vector frames;
if (payload.size() <= SF_MAX) { // 单帧 SF
Bytes f(CAN_DL, 0x00);
f[0] = static_cast (payload.size() & 0x0F);
std::copy(payload.begin(), payload.end(), f.begin() + 1);
frames.push_back(std::move(f));
return frames;
}
Bytes ff(CAN_DL, 0x00); // 首帧 FF
ff[0] = static_cast ( 0x10 | ((payload.size() >> 8) & 0x0F));
ff[1] = static_cast (payload.size() & 0xFF);
std::copy(payload.begin(), payload.begin() + FF_MAX, ff.begin() + 2);
frames.push_back(std::move(ff));
std::size_t off = FF_MAX; // 连续帧 CF
Byte sn = 1;
while (off < payload.size()) {
Bytes cf(CAN_DL, 0x00);
cf[0] = static_cast ( 0x20 | (sn & 0x0F));
const std::size_t n = std::min(CF_MAX, payload.size() - off);
std::copy(payload.begin() + off, payload.begin() + off + n, cf.begin() + 1);
frames.push_back(std::move(cf));
off += n;
sn = static_cast ((sn + 1) & 0x0F);
}
return frames;
}
// ---- 接收侧:CAN 帧 -> UDS 报文 ----
class Reassembler {
public:
// 返回 nullopt 表示尚未收全(或收到非法帧)
std::optional push(const Bytes& frame) {
if (frame.empty()) return std::nullopt;
const Byte pci = static_cast (frame[ 0] >> 4);
switch (pci) {
case 0x0: { // 单帧
const std::size_t len = frame[0] & 0x0F;
if (len == 0 || len > SF_MAX || frame.size() < len + 1)
return std::nullopt;
return Bytes(frame.begin() + 1, frame.begin() + 1 + len);
}
case 0x1: { // 首帧
if (frame.size() < 2) return std::nullopt;
total_ = ((static_cast(frame[0]) & 0x0F) << 8) | frame[1];
buffer_.assign(frame.begin() + 2, frame.end());
expectedSn_ = 1;
return tryFinish();
}
case 0x2: { // 连续帧
if (total_ == 0) return std::nullopt; // 没有首帧先到
if ((frame[0] & 0x0F) != expectedSn_) { reset(); return std::nullopt; }
expectedSn_ = static_cast ((expectedSn_ + 1) & 0x0F);
buffer_.insert(buffer_.end(), frame.begin() + 1, frame.end());
return tryFinish();
}
default:
return std::nullopt; // 流控帧 FC,本示例不处理
}
}
void reset() { buffer_.clear(); total_ = 0; expectedSn_ = 1; }
private:
std::optional tryFinish() {
if (total_ == 0 || buffer_.size() < total_) return std::nullopt;
Bytes out(buffer_.begin(), buffer_.begin() + total_);
reset();
return out;
}
Bytes buffer_;
std::size_t total_ = 0;
Byte expectedSn_ = 1;
};
};
// ============================================================================
// 3. UDS 服务端(ECU 侧)
// ============================================================================
struct Dtc {
std::uint32_t code; // 3 字节 DTC
Byte status; // 状态掩码(bit3 = confirmedDTC)
};
class UdsServer {
public:
using TxFn = std::function;
explicit UdsServer(TxFn tx) : tx_(std::move(tx)) {
// 预置数据
const char* vin = "LSVAA1234567890AB"; // 17 位 VIN
dataStore_[0xF190] = Bytes(vin, vin + 17);
dataStore_[0xF18C] = Bytes{0x01, 0x02, 0x03, 0x04}; // ECU 序列号
dtcs_.push_back({0x00A001, 0x08}); // confirmedDTC
dtcs_.push_back({0x00B002, 0x09}); // confirmed + testFailed
}
// 入口:喂入一帧 CAN 报文
void onCanFrame(const Bytes& frame) {
auto req = rx_.push(frame);
if (!req) return; // 还没收全
Bytes resp = handleRequest(*req);
if (resp.empty()) return; // 被抑制或无响应
for (const auto& f : IsoTp::segment(resp)) tx_(f);
}
private:
// ---------------- 服务分发 ----------------
Bytes handleRequest(const Bytes& req) {
if (req.empty()) return {};
const Byte sid = req[0];
Bytes resp;
switch (sid) {
case 0x10: resp = svcSessionControl(req); break;
case 0x11: resp = svcEcuReset(req); break;
case 0x14: resp = svcClearDtc(req); break;
case 0x19: resp = svcReadDtc(req); break;
case 0x22: resp = svcReadDataById(req); break;
case 0x27: resp = svcSecurityAccess(req); break;
case 0x2E: resp = svcWriteDataById(req); break;
case 0x3E: resp = svcTesterPresent(req); break;
default:
return negativeResponse(sid, Nrc::ServiceNotSupported);
}
// 统一的“抑制正响应位”处理(子功能 bit7)
if (!resp.empty() && resp[0] != kNegativeResponseSid && suppressPosRsp(req))
return {};
return resp;
}
bool suppressPosRsp(const Bytes& req) const {
if (req.size() < 2) return false;
switch (req[0]) {
case 0x10: case 0x11: case 0x19: case 0x27:
case 0x28: case 0x31: case 0x3E:
return (req[1] & 0x80u) != 0;
default:
return false;
}
}
// ---------------- 0x10 诊断会话控制 ----------------
Bytes svcSessionControl(const Bytes& req) {
if (req.size() != 2) return negativeResponse(req[0], Nrc::IncorrectMessageLength);
const Byte sub = static_cast (req[ 1] & 0x7F);
Session s;
switch (sub) {
case 0x01: s = Session::Default; break;
case 0x02: s = Session::Programming; break;
case 0x03: s = Session::Extended; break;
default: return negativeResponse(req[0], Nrc::SubFunctionNotSupported);
}
session_ = s;
securityLevel_ = 0; // 切会话必须重新做安全访问
seed_ = 0;
restartS3Timer();
// P2 = 50 ms (0x0032),P2* = 5000 ms = 500 × 10 ms (0x01F4)
return positiveResponse(req[0], { sub, 0x00, 0x32, 0x01, 0xF4 });
}
// ---------------- 0x11 ECU 复位 ----------------
Bytes svcEcuReset(const Bytes& req) {
if (req.size() != 2) return negativeResponse(req[0], Nrc::IncorrectMessageLength);
const Byte sub = static_cast (req[ 1] & 0x7F);
if (sub < 0x01 || sub > 0x03)
return negativeResponse(req[0], Nrc::SubFunctionNotSupported);
session_ = Session::Default; // 复位后回到默认会话
securityLevel_ = 0;
return positiveResponse(req[0], { sub }); // 真实 ECU 随后会重启
}
// ---------------- 0x14 清除诊断信息 ----------------
Bytes svcClearDtc(const Bytes& req) {
if (req.size() != 4) return negativeResponse(req[0], Nrc::IncorrectMessageLength);
const std::uint32_t group =
(static_cast(req[1]) << 16) |
(static_cast(req[2]) << 8) |
static_cast(req[3]);
if (group == 0xFFFFFFu) {
dtcs_.clear();
} else {
dtcs_.erase(std::remove_if(dtcs_.begin(), dtcs_.end(),
[group](const Dtc& d) { return d.code == group; }),
dtcs_.end());
}
return positiveResponse(req[0]);
}
// ---------------- 0x19 读取 DTC 信息(子功能 0x02) ----------------
Bytes svcReadDtc(const Bytes& req) {
if (req.size() != 3) return negativeResponse(req[0], Nrc::IncorrectMessageLength);
const Byte sub = static_cast (req[ 1] & 0x7F);
const Byte mask = req[2];
if (sub != 0x02) return negativeResponse(req[0], Nrc::SubFunctionNotSupported);
Bytes data;
data.push_back(sub);
data.push_back(0xFF); // DTCStatusAvailabilityMask
for (const auto& d : dtcs_) {
if ((d.status & mask) == 0) continue;
data.push_back(static_cast ((d.code >> 16) & 0xFF));
data.push_back(static_cast ((d.code >> 8) & 0xFF));
data.push_back(static_cast ( d.code & 0xFF));
data.push_back(d.status);
}
return positiveResponse(req[0], data);
}
// ---------------- 0x22 按标识符读数据 ----------------
Bytes svcReadDataById(const Bytes& req) {
if (req.size() < 3 || ((req.size() - 1) % 2) != 0)
return negativeResponse(req[0], Nrc::IncorrectMessageLength);
Bytes data;
for (std::size_t i = 1; i + 1 < req.size(); i += 2) {
const std::uint16_t did =
static_cast((req[i] << 8) | req[i + 1]);
auto it = dataStore_.find(did);
if (it == dataStore_.end())
return negativeResponse(req[0], Nrc::RequestOutOfRange);
data.push_back(req[i]);
data.push_back(req[i + 1]);
data.insert(data.end(), it->second.begin(), it->second.end());
}
return positiveResponse(req[0], data);
}
// ---------------- 0x2E 按标识符写数据 ----------------
Bytes svcWriteDataById(const Bytes& req) {
if (req.size() < 4)
return negativeResponse(req[0], Nrc::IncorrectMessageLength);
if (securityLevel_ == 0) // 未解锁 -> 拒绝
return negativeResponse(req[0], Nrc::SecurityAccessDenied);
const std::uint16_t did =
static_cast((req[1] << 8) | req[2]);
dataStore_[did] = Bytes(req.begin() + 3, req.end());
return positiveResponse(req[0], { req[1], req[2] });
}
// ---------------- 0x27 安全访问(Seed / Key) ----------------
Bytes svcSecurityAccess(const Bytes& req) {
if (req.size() < 2) return negativeResponse(req[0], Nrc::IncorrectMessageLength);
const Byte sub = static_cast (req[ 1] & 0x7F);
const bool isSeedReq = (sub % 2 == 1); // 奇数 = 请求种子
if (session_ == Session::Default) // 默认会话不允许安全访问
return negativeResponse(req[0], Nrc::ServiceNotSupportedInSession);
// ---- 请求种子 ----
if (isSeedReq) {
if (req.size() != 2)
return negativeResponse(req[0], Nrc::IncorrectMessageLength);
seed_ = nextSeed();
expectedKey_ = computeKey(seed_);
return positiveResponse(req[0], {
sub,
static_cast (seed_ >> 8),
static_cast (seed_ & 0xFF)
});
}
// ---- 发送密钥 ----
if (req.size() != 4)
return negativeResponse(req[0], Nrc::IncorrectMessageLength);
if (seed_ == 0) // 没先要种子
return negativeResponse(req[0], Nrc::RequestSequenceError);
const std::uint16_t key =
static_cast((req[2] << 8) | req[3]);
if (key != expectedKey_) {
seed_ = 0; // 密钥错必须重新取种子
if (++failedAttempts_ >= 3)
return negativeResponse(req[0], Nrc::ExceedNumberOfAttempts);
return negativeResponse(req[0], Nrc::InvalidKey);
}
seed_ = 0;
failedAttempts_ = 0;
securityLevel_ = static_cast ((sub + 1) / 2); // 0x02 -> level 1
return positiveResponse(req[0], { sub });
}
// ---------------- 0x3E Tester Present ----------------
Bytes svcTesterPresent(const Bytes& req) {
if (req.size() != 2)
return negativeResponse(req[0], Nrc::IncorrectMessageLength);
restartS3Timer(); // 保活,防止 S3 超时回落
return positiveResponse(req[0], { static_cast (req[ 1] & 0x7F) });
}
// ---------------- 工具函数 ----------------
void restartS3Timer() { s3Tick_ = 0; } // 真实实现为 5s 定时器
static std::uint16_t nextSeed() {
// 教学用确定性伪随机;真实 ECU 必须使用硬件随机数
static std::uint32_t s = 0xDEADBEEFu;
s = s * 1103515245u + 12345u;
return static_cast((s >> 16) & 0xFFFFu);
}
TxFn tx_;
IsoTp::Reassembler rx_;
Session session_ = Session::Default;
Byte securityLevel_ = 0;
std::uint16_t seed_ = 0;
std::uint16_t expectedKey_ = 0;
int failedAttempts_= 0;
int s3Tick_ = 0;
std::map dataStore_;
std::vector dtcs_;
};
// ============================================================================
// 4. 虚拟 CAN 总线 + 测试仪(客户端)演示
// ============================================================================
struct Bus {
std::function toEcu;
std::function toTester;
void testerSend(const Bytes& f) const { if (toEcu) toEcu(f); }
void ecuSend (const Bytes& f) const { if (toTester) toTester(f); }
};
} // namespace uds
// ============================================================================
// main
// ============================================================================
int main() {
using namespace uds;
// ---------- 搭一条虚拟 CAN 总线 ----------
Bus bus;
UdsServer ecu([&bus](const Bytes& f) { bus.ecuSend(f); });
IsoTp::Reassembler testerRx;
std::vector rxQueue;
bus.toEcu = [&ecu](const Bytes& f) { ecu.onCanFrame(f); };
bus.toTester = [&testerRx, &rxQueue](const Bytes& f) {
if (auto msg = testerRx.push(f)) rxQueue.push_back(std::move(*msg));
};
// ---------- 工具 ----------
auto hex = [](const Bytes& b) {
std::ostringstream os;
for (std::size_t i = 0; i < b.size(); ++i) {
if (i) os << ' ';
os << std::hex << std::uppercase << std::setw(2) << std::setfill('0')
<< static_cast(b[i]);
}
return os.str();
};
auto transact = [&](const Bytes& req) -> Bytes {
rxQueue.clear();
testerRx.reset();
for (const auto& f : IsoTp::segment(req)) bus.testerSend(f); // ISO-TP 切分发送
return rxQueue.empty() ? Bytes{} : rxQueue.back();
};
auto step = [&](const std::string& title, const Bytes& req) -> Bytes {
Bytes rsp = transact(req);
std::cout << " " << title << "\n"
<< " TX: " << hex(req) << "\n"
<< " RX: " << (rsp.empty() ? std::string("(无响应/被抑制)") : hex(rsp))
<< "\n";
return rsp;
};
std::cout << "=============== UDS 诊断交互演示 ===============\n\n";
// ---------------------------------------------------------------- [1]
std::cout << "[1] 会话控制与保活\n";
step("TesterPresent (3E 00)", {0x3E, 0x00});
step("进入扩展会话 (10 03)", {0x10, 0x03});
step("进入编程会话 (10 02)", {0x10, 0x02});
step("回到默认会话 (10 01)", {0x10, 0x01});
// ---------------------------------------------------------------- [2]
std::cout << "\n[2] 读数据(默认会话即可,注意多帧响应)\n";
step("读 VIN (22 F1 90)", {0x22, 0xF1, 0x90});
step("读 ECU SN (22 F1 8C)", {0x22, 0xF1, 0x8C});
step("读不存在的 DID", {0x22, 0x12, 0x34});
// ---------------------------------------------------------------- [3]
std::cout << "\n[3] 写数据被安全访问拦截\n";
step("默认会话写 VIN", {0x2E, 0xF1, 0x90, 0x41});
// ---------------------------------------------------------------- [4]
std::cout << "\n[4] 安全访问 0x27 Seed/Key 握手\n";
step("默认会话请求种子(应拒绝)", {0x27, 0x01});
step("进入扩展会话 (10 03)", {0x10, 0x03});
Bytes rsp = step("请求种子 (27 01)", {0x27, 0x01});
std::uint16_t seed = 0;
if (rsp.size() >= 4 && rsp[0] == 0x67)
seed = static_cast((rsp[2] << 8) | rsp[3]);
std::cout << " → 解析出种子 = 0x" << std::hex << std::uppercase << seed
<< std::dec << "\n";
step("故意发送错误密钥", {0x27, 0x02, 0x00, 0x00});
rsp = step("重新请求种子", {0x27, 0x01});
seed = static_cast((rsp[2] << 8) | rsp[3]);
const std::uint16_t key = computeKey(seed);
std::cout << " → 本地算出密钥 = 0x" << std::hex << std::uppercase << key
<< std::dec << "\n";
step("发送正确密钥", {0x27, 0x02,
static_cast (key >> 8),
static_cast (key & 0xFF)});
// ---------------------------------------------------------------- [5]
std::cout << "\n[5] 写入数据(20 字节请求,走 ISO-TP 多帧)\n";
const std::string newVin = "LSVAA0000000000123";
Bytes writeReq{0x2E, 0xF1, 0x90};
writeReq.insert(writeReq.end(), newVin.begin(), newVin.end());
step("写入 VIN (2E F1 90 ...)", writeReq);
rsp = step("读回 VIN 验证", {0x22, 0xF1, 0x90});
if (rsp.size() > 3) {
std::string vin(rsp.begin() + 3, rsp.end());
std::cout << " → VIN = " << vin << "\n";
}
// ---------------------------------------------------------------- [6]
std::cout << "\n[6] 故障码 DTC\n";
step("读取全部 DTC (19 02 FF)", {0x19, 0x02, 0xFF});
step("只读 testFailed 的 DTC", {0x19, 0x02, 0x01});
step("清除全部 DTC (14 FFFFFF)",{0x14, 0xFF, 0xFF, 0xFF});
step("再次读取 DTC", {0x19, 0x02, 0xFF});
// ---------------------------------------------------------------- [7]
std::cout << "\n[7] 异常与边界路径\n";
step("未实现的服务 0x28", {0x28, 0x00, 0x03});
step("长度非法的 0x22", {0x22, 0xF1});
step("抑制正响应 (3E 80)", {0x3E, 0x80});std::cout << "\n================= 演示结束 =================\n";
return 0;
}
热门跟贴