[AUTOSAR][诊断管理]什么是UDS诊断? 实现的方式是怎么样的?
项目总结
项目概述
该项目是一个基于OSU(统一协议)的诊断框架开发项目,主要用于实现汽车或系统中的故障检测与排除功能。代码主要包含两个部分:diagmain.c 和 diagnwl.h 等相关文件。
主要模块与功能
diag_main.c
- 实现了诊断主程序的核心逻辑。
- 通过NWL(网络层)服务调用网络层数据包的接收与发送操作。
- 提供多种检测方法(如DTC服务)、数据包接收处理以及错误响应机制。
- 实现了多个NWL服务实例,并通过CAN总线进行通信。
diag_nwl.h- 定义了网络层数据单元帧类型(如单帧SF、首帧FF等)。
- 定义了链路层寻址类型(如物理地址和功能性地址)。
- 定义了网络层状态枚举(如空闲态、发送态等)。
- 包含多种状态转换逻辑和异常处理机制。
diag_main.h- 定义了全局变量和常量。
- 包含多种服务类型(如基本服务、扩展服务等)及其对应的操作方法。
- 定义了诊断功能的基本参数配置方式。
关键数据类型
nwlpdut: 表示网络层传输的数据包头信息。
nwlmsgt: 表示网络层传输的数据包内容。
nwlresultst_t: 表示接收到的故障报文结果状态。
核心功能
初始化与关闭- 使用OSM初始化NWL服务器,并通过配置管理子命令启动或关闭NWL服务器。
接收与发送数据包- sendnrcdata: 发送否定响应报文。
- diagnostics_dispose: 接收并处理故障报文并清理相关资源。
异常处理- NRCGENERALPROGRAMMING_FAILURE: 服务器在擦除或写入Flash时发生错误。
- NRCNOSECURITY_LEVEL: 服务器不支持当前的安全级别。
检测方法- DTC检测方法:用于检测ECU内部电路是否正常工作的三种检测方法(按需求字节读取连续DTC信息并进行检查)。
多任务处理- 使用多个线程循环发送数据包以提高性能。
技术特点
基于OSU协议实现了跨平台的诊断功能开发。
提供灵活的服务配置选项和异常处理机制,确保系统的健壮性与可维护性。
使用CAN总线作为本地通信接口,并通过IP消息交换实现远程节点间的通信。
应用场景
适用于汽车控制单元或其他需要远程监控与故障排除的应用场景。
支持ECU间的数据通信与报文处理,能够高效地完成故障检测任务。
文章目录
- 一、UDS诊断
-
- (1) 概念
- (2)目录介绍
- (3)层次类型
-
- ①物理层
- ②链路层
- ③协议层
- ④数据接收
第4节 帧类型说明 diag_nwl.h
* (5)ISO14229-1协议定义了6类功能,26种服务
-
二、UDS服务
-
- (1)模式类型
-
三、示例代码
-
- (1)diag_main.c
- (2)diag_main.h
- (3)diag_nwl.h
一、UDS诊断
(1) 概念
UDS全称是UnifiedDiagnosticServices,在国际标准ISO 14229-1中有明确规定。目前广泛采用的标准包括ISO 14230、ISO 15031以及 ISO 15765等协议体系,在这些体系中非常熟知的就是 ISO 14229即 UDS 协议体系。
在 UDS 标准体系中不仅明确了服务功能与数据格式的规定内容之外,并且还对标准化的数据进行了详细规定,在 OES 装备采用 UDS 协议时必须遵循标准规定的各项服务功能与数据格式的同时还需要根据自身实际需求补充具有特定属性的数据类型。
实现车辆故障诊断需要具备Tester端设备与ECU端设备之间的有效通信这一过程通常采用一问一答的方式展开因此Tester端与 ECU 端都需要严格遵循统一的诊断通信协议。
诊断仪作为客户端或测试端设备而ECU则作为服务端设备;在工作流程上ECU(服务端)会根据接收到的诊断服务指令按照预先设定好的机制返回相应的诊断响应结果。
从总体架构上讲UDS系统主要包含有物理层链路层协议基层网络层以及应用层等多个层次结构通过层层递进的方式实现了复杂业务功能的有效支撑。
(2)目录介绍
│ diag_main.c // 诊断主函数:帧收发,执行对应诊断服务,定时器计数
│ diag_main.h
│ diag_nwl.h // 帧类型定义
│ README.md
│
├─DTC [故障日记]
│ dtc_main.c // DTC主函数:故障扫描,读写flash
│ dtc_main.h
│ DTC状态位.png
│ README.md
│
└─UDS [UDS服务]
10_session_ctl.c // 会话模式控制
11_ecu_reset.c // ECU 复位功能
14_cls_dtc_info.c // 清除DTC信息
19_read_dtc_info.c // 读取DTC信息
22_read_data_by_id.c // 通过ID读取数据
27_security_access.c // 安全访问
28_comm_ctl.c // 通信控制
2e_write_data_by_id.c // 通过ID写入数据
2f_io_ctl_by_id.c // 通过ID控制IO
31_routine_ctl.c // 例程控制
34_req_dowload.c // 请求下载
35_req_upload.c // 请求上传(未实现)
36_transfer_data.c // 数据传输
37_req_transfer_exit.c // 请求退出传输。终止数据传输的(上传/下载)
3e_test_present.c // 测试设备在线,保持当前会话不退出。
85_ctl_dtc_set.c // 设置DTC功能
(3)层次类型
①物理层
物理层的主要功能可以由任何协议的硬件架构来实现这一设计基础得到了链路层的支持。
基于CAN协议作为传输介质的情况下物理层可以通过相应的CAN芯片来进行系统架构设计。
当选择以LAN技术作为传输介质时同样也可以通过相应的芯片实现系统的构建过程。
②链路层
链路层是整个协议的基础支撑(从软件工程师角度来看),它规定了当前系统基础数据架构与底层驱动的选择及构建方式。
这一层面的整体稳定性对于整个网络的实际运行至关重要。
其直接影响了数据接收的准确性以及误码率,并对反馈码的传输完整性产生重大影响,在许多上层功能无法直接识别的问题中都可能成为关键因素。
③协议层
- 协议层主要进行以下几大操作:数据接收、数据处理、数据反馈、时间限制。
④数据接收
- UDS主要基于硬件协议构建数据架构,并采用行业通用的CAN总线协议完成数据传输。值得注意的是,在处理大量信息时,CAN框架固定长度会带来一定的传输压力。
- UDS包含四类标准数据框:标准帧(Normal Frame)、首帧(First Frame)、流控帧(Constraints Frame)和数据帧(Data Frame)。这些不同类型的框架在功能上有明确区分。
- 各个具体的数据定义则主要依据位于CAN单帧第一位字节前四位Bit的信息来确定。
| 数据帧定义 | 数据帧标识 | 数据实体示例 |
|---|---|---|
| 标准帧 | 00 | 00 XX XX XX XX XX XX XX |
| 首帧 | 01 | 1X XX XX XX XX XX XX XX |
| 流控帧 | 11 | 30 AA BB XX XX XX XX XX |
| 数据帧 | 10 | 20 XX XX XX XX XX XX XX |
(4)帧类型介绍 diag_nwl.h
单帧(SF)

单帧则较为便捷,主要用于传输较短的指令序列。其优点在于快速准确且高效地完成任务,并且每个帧能够直接对应一个指令的操作。然而,在大多数应用场景中难以实现这一点。
其主要数据格式定义如下:
| 数据类型 | 数据长度 (bit) | 备注 |
|---|---|---|
| 帧长度 | 0-3 | 有效字节数 [0-7] |
| 帧类型 | 4-7 | 无效值为0 |
| 数据实体 | 8-63 | 数据内容依据帧长度进行解析 |
首帧(FF)(0x1x)

首帧的是整个多帧传输流程的起始,一般由客户端先发出,包括了当前指令请求的长度和先前的几个数据。
其主要的数据格式定义为: 帧长度 = (帧长度1 << 4 | 帧长度2). 整个多帧的有效数据长度 占用1个半字节。max:0xFFF,4095 数据定义 | 长度bit | 备 注 -- | -- | -- | 帧长度1 | 0~3 | 整个多帧的有效数据长度 高4bit 帧类型 | 4~7 | 1 帧长度2 | 8~15 | 整个多帧的有效数据长度 低8bit 数据实体 | 16~63 | 一部分数据
流控帧(FC)(0x30)

该流程的第一帧通常由客户端发起,并包含了当前指令请求所需的长度信息以及若干先前的数据字段。
数据帧(CF)(0x2x) x:序号 ,0x0~0xF循环

数据帧字段是在服务端发送完流量控制报文后,在客户端发送相关数据前的时间段内产生的字段类型。这些字段构成了大量帧格式的基础结构,并且是程序编写过程中容易出现逻辑错误的关键组成部分。
其主要的数据格式定义如下:
| 数据定义 | 长度(bit) | 备注 |
|---|---|---|
| SN | 0-3 | 循环计数值(范围:0x0到0xF) |
| 帧类型 | 4-7 | 类型标识位 |
| 数据实体 | 8-63 | DATA0至DATA7 |
举例:

(5)ISO14229-1协议定义了6类功能,26种服务
功能模块划分如下:
- 覆盖诊断与通信管理相关领域的产品功能模块共计提供10种具体实现方案;
- 数据传输模块涵盖多种实现方式共计提供7种解决方案;
- 存储类数据传输模块仅包含两种核心方案;
- 输入输出控制模块仅包含一种完整方案;
- 日常操作流程模块仅有一种标准流程方案;
- 上传与下载控制模块提供五种不同实现方式。
二、UDS服务
(1)模式类型
UDS中的一个核心软件表现形式是其作为一个抽象体系的重要组成部分,在应用层中它被划分为三大类模式:基础类型、增强类型以及仅在Boot存在时使用的编程类型。其中UDS具体实现了普通模式、扩展模式以及针对Boot系统的编程模式(后两者仅在Boot阶段存在)。
在扩展与编程模式中,具体可分为普通权限类型与特权类型。主要区别体现在以下方面:包括功能模块划分、权限控制策略以及资源管理等方面。
| 模式名称 | 授权模式 | 具有权限 |
|---|---|---|
| 普通模式 | 普通权限模式 | 无法读取内部数据,执行较为简单的指令(重启、模式切换、会话保持) |
| 普通模式 | 特权模式 | 无此模式 |
| 扩展模式 | 普通权限模式 | 可以读取内部的ECU状态值,ECU错误值,厂商SN号等 |
| 扩展模式 | 特权模式 | 可以读写内部的ECU错误值、厂商SN号等 |
| 编程模式 | 普通权限模式 | 可以读取一些厂商内部值 |
| 编程模式 | 特权模式 | 可以读写厂商内部值、可以刷新当前的固件版本 |
三、示例代码
(1)diag_main.c
/******************************************************************************** * @file diag_main.c
* @author jianqiang.xue
* @version V1.0.0
* @date 2023-05-18
* @brief 诊断主函数
* 未占用数据域字节需要用数据填充。推荐请求消息填充 0x55,应答消息填充 0xaa。
********************************************************************************/
/* Includes ------------------------------------------------------------------*/
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include "modules.h"
#include "os_api.h"
#include "edebug.h"
#include "atcmd_slave.h" // 自行添加[Module\atcmd\atcmd_slave.c]
/* Private includes ----------------------------------------------------------*/
#include "mcu_cpu.h"
#include "mcu_intc.h"
#include "mcu_uart.h"
#include "std_math.h"
#include "app_can.h"
#include "can_nm.h"
#include "app_nm.h"
#include "diag_main.h"
#if AUTOSAR_DIAG_DTC_SWITCH
#include "dtc_main.h"
#endif
/* Private define ------------------------------------------------------------*/
#define P2_TIMER_BASE (10) // ms
#define P3_TIMER_BASE (1000) // ms
/* Private typedef -----------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
nwl_msg_t g_rx_msg; // 连续帧数据
nwl_msg_t g_rx_sf_msg; // 单帧数据
nwl_msg_t g_tx_msg;
extern diag_service_ins_t diag_srvc$$Base;
extern diag_service_ins_t diag_srvc$$Limit;
diag_info_t g_diag_info = {
.session = DEFAULT_SESSION,
.security_level = DIAG_NO_SECURITY_LEVEL,
};
static uint8_t g_flow_contrl_interval_time = 1; // 连续帧时间间隔ms
uint16_t g_p2_service_time_remaining = 0; // P2服务剩余时间,超时回复NRC78
uint16_t g_session_time_remaining = 0; // 会话剩余时间
extern dfu_info_t g_dfu_info;
/*****************线程创建*****************/
OS_THREAD_DECLARE(diag_main, OS_PRIORITY_NORMAL, 1024+256);
/***************软定时器创建***************/
OS_TIMER_DECLARE_N(p2_timeout);
OS_TIMER_DECLARE_N(ss_timeout);
/* Private func --------------------------------------------------------------*/
/** * @brief 处理收到的诊断数据包(完整包)
* @param *p:诊断数据包指针
*/
static void diag_service_dispose(nwl_msg_t *p) {
bool match = false;
diag_service_ins_t* service_list;
#if AUTOSAR_DIAG_SWITCH
for (service_list = &diag_srvc$$Base; service_list < &diag_srvc$$Limit; service_list++) {
// 针对FLASH默认值为0xFF时,不进行比较
if (service_list->diag_num != 0xFF && service_list->diag_num != 0xAA && service_list->diag_num != 0x00) {
if (service_list->diag_num == p->data[0]) {
match = true;
break;
}
} else {
match = false;
break;
}
}
#endif
if (match == false) { // 没有寻找到该服务
LOGE("No such service,0x%02x", p->data[0]);
send_nrc_data(p->data[0], NRC_SERVICE_NOT_SUPPORTED);
return;
}
LOGI("Find service,0x%02x|sub:0x%02x|%x", p->data[0], p->data[1], service_list->main_func);
// 检测服务主函数为空 or 不支持的寻址类型
if ((service_list->main_func == NULL) || (service_list->main_func == (void*)0xFFFFFFFF) ||
(service_list->main_func == (void*)0xAAAAAAAA) || !(service_list->spport_addr_t & p->ta_type)) {
LOGE("main_func is null, or not spport_addr_t.%02x,%02x!=%02x",
service_list->main_func, service_list->spport_addr_t, p->ta_type);
send_nrc_data(service_list->diag_num, NRC_SERVICE_NOT_SUPPORTED);
return;
}
// 检测会话支持
if (!(service_list->spport_session & g_diag_info.session)) {
LOGE("This session is not supported,0x%02x!=0x%02x",
service_list->spport_session, g_diag_info.session);
send_nrc_data(service_list->diag_num, NRC_SERVICE_NOT_SUPPORTED_INACTIVE_SESSION);
return;
}
// 检测安全等级
if (!(service_list->security_level & g_diag_info.security_level)) {
LOGE("The security level is incorrect,0x%02x!=0x%02x",
service_list->security_level, g_diag_info.security_level);
send_nrc_data(service_list->diag_num, NRC_ACCESS_DENIED);
return;
}
os_delay(2);
#if defined(DESC_ENABLE_PREHANDLER_USAGE)
if(service_list->pre_func != NULL) // 预处理函数
(service_list->pre_func)();
#endif
service_list->main_func(p); // 主处理函数
#if defined(DESC_ENABLE_POSTHANDLER_USAGE)
if(service_list->post_func != NULL) // 后处理函数
service_list->post_func(0);
#endif
}
static void timer_p2_timeout_cb(void const *arg) {
diag_main_send_signal(SIGNAL_P2_SERVICE_CHECK);
}
static void timer_ss_timeout_cb(void const *arg){
diag_main_send_signal(SIGNAL_P3_SESSION_CHECK);
}
/** * @brief [线程回调函数][diag_main任务] 处理诊断业务功能, 目的是为了分包发送数据。
*/
static void task_diag_main(void const *argument) {
os_event event;
uint8_t id = 0; // 帧SN
while (1) {
event = os_signal_wait(0, OS_WAIT_FOREVER);
if (event.status != OS_EVENT_SIGNAL)
continue;
if (event.value.signals & SIGNAL_DIAG_CF_DATA_DISPOSE) { // 收到连续帧
g_p2_service_time_remaining = P2_SERVER_MAX;
g_session_time_remaining = S3_SERVER_MAX;
OS_TIMER_RESTART(p2_timeout, P2_TIMER_BASE);
diag_service_dispose(&g_rx_msg);
if (g_rx_msg.data != NULL) {
free(g_rx_msg.data);
g_rx_msg.data = NULL;
g_rx_msg.len = 0;
}
OS_TIMER_STOP(p2_timeout);
g_rx_msg.ta_type = 0;
g_rx_msg.sn = 0;
os_delay(1);
}
if (event.value.signals & SIGNAL_DIAG_SF_DATA_DISPOSE) { // 收到单帧
g_p2_service_time_remaining = P2_SERVER_MAX;
g_session_time_remaining = S3_SERVER_MAX;
OS_TIMER_RESTART(p2_timeout, P2_TIMER_BASE);
diag_service_dispose(&g_rx_sf_msg);
if (g_rx_sf_msg.data != NULL) {
free(g_rx_sf_msg.data);
g_rx_sf_msg.data = NULL;
}
OS_TIMER_STOP(p2_timeout);
g_rx_sf_msg.ta_type = 0;
os_delay(1);
}
if (event.value.signals & SIGNAL_DIAG_SEND_DATA) { // 分帧发送(请求发送方继续发送CF)
if (g_tx_msg.data != NULL) {
uint16_t len = g_tx_msg.len;
if (len == 0) {
LOGE("DIAG_SEND_DATA_LEN");
goto send_end;
}
//LOGI("%u,%u", len, g_flow_contrl_interval_time);
uint8_t buff[8] = {0};
buff[0] = 0x21 + id;
if (len > 7) {
memcpy(buff + 1, (g_tx_msg.data + 8 + (id * 7)), 7);
len -= 7;
} else {
memcpy(buff + 1, (g_tx_msg.data + 8 + (id * 7)), len);
// 填充0xAA
for (uint8_t i = 0; i < 7 - len; i++) {
buff[1 + len + i] = 0xAA;
}
len = 0;
}
id++;
// LOGD("%u|%02x %02x %02x %02x %02x %02x %02x %02x", len,
// buff[0], buff[1], buff[2], buff[3], buff[4], buff[5], buff[6], buff[7]);
app_can_enqueue_msg(CAN_MSG_EVENT_SEND, NWL_RES_ADDR, buff, 8);
g_tx_msg.len = len;
// g_p2_service_time_remaining = 0; // 如果发送诊断报文,则清除倒计时。P2_SERVER_MAX
os_delay(g_flow_contrl_interval_time);
if (g_tx_msg.len != 0) {
diag_main_send_signal(SIGNAL_DIAG_SEND_DATA);
continue;
}
send_end:
free(g_tx_msg.data);
g_tx_msg.data = NULL;
g_rx_msg.len = 0;
g_rx_msg.sn = 0;
id = 0;
} else {
LOGE("DIAG_SEND_DATA");
}
}
#if AUTOSAR_DIAG_DTC_SWITCH // DTC开关控制
if (event.value.signals & SIGNAL_DIAG_DTC_EN) { // DTC功能使能
set_dtc_switch(true);
}
if (event.value.signals & SIGNAL_DIAG_DTC_DIS) { // DTC功能关闭
set_dtc_switch(false);
}
if (event.value.signals & SIGNAL_DIAG_DTC_CLS) { // DTC功能清除全部
clear_dtc_info();
}
if (event.value.signals & SIGNAL_DIAG_DTC_SAVE) { // DTC功能清除全部
save_dtc_info();
}
#endif // DTC End
if (event.value.signals & SIGNAL_P2_SERVICE_CHECK) {
if (g_p2_service_time_remaining != 0) {
if (g_p2_service_time_remaining < P2_TIMER_BASE)
g_p2_service_time_remaining = 0;
else
g_p2_service_time_remaining -= P2_TIMER_BASE;
if (g_p2_service_time_remaining == 0) {
if (g_rx_msg.ta_type != 0) {
send_nrc_data(g_rx_msg.data[2], 0x78);
} else if (g_rx_sf_msg.ta_type != 0) {
send_nrc_data(g_rx_sf_msg.data[1], 0x78);
}
LOGI("response timeout. FC_T:%u, SF_T:%u", g_rx_msg.ta_type, g_rx_sf_msg.ta_type);
}
}
}
if (event.value.signals & SIGNAL_P3_SESSION_START) {
g_session_time_remaining = S3_SERVER_MAX;
OS_TIMER_RESTART(ss_timeout, P3_TIMER_BASE);
}
if (event.value.signals & SIGNAL_P3_SESSION_CHECK) {
if (g_session_time_remaining != 0) {
if (g_session_time_remaining < P3_TIMER_BASE)
g_session_time_remaining = 0;
else
g_session_time_remaining -= P3_TIMER_BASE;
if (g_session_time_remaining == 0) {
if (g_diag_info.session != DEFAULT_SESSION) {
g_diag_info.session = DEFAULT_SESSION;
g_diag_info.security_level = DIAG_NO_SECURITY_LEVEL;
g_dfu_info.crc32 = INITIAL_REMAINDER;
LOGI("Session timeout, switch default.");
}
OS_TIMER_STOP(ss_timeout);
}
}
}
if (event.value.signals & SIGNAL_DIAG_RESET) {
// LOGI("RESET");
// os_delay(100);
__set_PRIMASK(1);
McuUartDeinit();
os_kernel_stop();
SCB->VTOR = 0;
NVIC_SystemReset();
while(1);
}
}
}
/* Public function ----------------------------------------------------------*/
/** * @brief [信号发送][diag_main信号] 用于给外部条用,发送信号给diag_main线程
* @param signals: 信号事件 参考diag_main.h的宏定义
*/
void diag_main_send_signal(int32_t signals) {
isr_signal_set(task_diag_main_id, signals);
}
/** * @brief 网络层服务参数初始化
*/
void diag_main_init(void) {
uint32_t timer_ret;
memset(&g_tx_msg, 0, sizeof(nwl_msg_t));
memset(&g_rx_msg, 0, sizeof(nwl_msg_t));
// 创建线程
OS_THREAD_CREATE(diag_main);
// 创建软定时器(等系统开启完毕后,再启动,警告这里启动定时器会导致os崩溃)
OS_TIMER_CREATE(p2_timeout, OS_TIMER_PERIODIC);
OS_TIMER_CREATE(ss_timeout, OS_TIMER_PERIODIC);
}
void diag_main_init_os(void) {
// OS_TIMER_START(p2_timeout, TIMER_BASE);
}
/** * @brief 处理收到的诊断数据(给CAN模块调用,识别帧数据,并由本函数组包)
* @param *p: 数据指针
*/
void diag_data_dispose(can_id_t id, uint8_t *data, uint8_t len) {
nwl_pdu_t pack;
pack.can_layout.id = id;
pack.can_layout.dlc = len;
// LOGD("CAN_RX(%02x):%02x %02x %02x %02x %02x %02x %02x %02x", id,
// data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
memcpy(pack.can_layout.data, data, 8);
LOGD("pci.type:%u,%02x", pack.pci.type, data[0]);
switch (pack.pci.type) {
case NWL_SINGLE_FRAME: // 单帧
if (g_rx_sf_msg.ta_type != 0)
break;
// 检查数据位是否合法。 长度等于0或大于7,则无效数据
if ((pack.pdu_layout.sf.pci.sf_dl == 0) || (pack.pdu_layout.sf.pci.sf_dl > 7)) {
LOGE("SF_DL:Illegal data len.(6.5.2.2)");
break;
}
// 检查 [数据接收长度] 是否 小于 [目标长度]
if (pack.pdu_layout.sf.dlc < (pack.pdu_layout.sf.pci.sf_dl + 1)) {
LOGE("DLC:Actual less than target.(7.4.4)");
break;
}
if (pack.can_layout.id == NWL_PHY_ADDR)
g_rx_sf_msg.ta_type = DIAG_PHYS_REQ;
else if (pack.can_layout.id == NWL_FUNC_ADDR)
g_rx_sf_msg.ta_type = DIAG_FUNC_REQ;
else {
g_rx_sf_msg.ta_type = 0;
break;
}
g_rx_sf_msg.data = malloc(pack.pdu_layout.sf.pci.sf_dl);
if (g_rx_sf_msg.data == NULL) {
LOGE("malloc fail.size:%u", g_rx_sf_msg.len);
break;
}
memcpy(g_rx_sf_msg.data, pack.pdu_layout.sf.data, pack.pdu_layout.sf.pci.sf_dl);
g_rx_sf_msg.len = pack.pdu_layout.sf.pci.sf_dl;
LOGD("SINGLE_FRAME");
diag_main_send_signal(SIGNAL_DIAG_SF_DATA_DISPOSE);
break;
case NWL_FIRST_FRAME: { // 首帧
nwl_pdu_t pack_tx = {0};
uint16_t data_len = (pack.pdu_layout.ff.pci.ff_dl_lo8) + ((pack.pdu_layout.ff.pci.ff_dl_hi4) << 8);
if (data_len < 8) {
LOGE("FF_DL: err data len.data_len:%u < 8", data_len);
break;
}
if (pack.pdu_layout.ff.dlc < 8) {
LOGE("DLC: err data len. dlc:%u < 8", pack.pdu_layout.ff.dlc);
break;
}
if (data_len > NWL_RX_MAX_LEN) {
pack_tx.pdu_layout.fc.pci.fs = NWL_FS_OVFLW;
LOGE("FF_DL: err data len.data_len:%u > MAX:%u", data_len, NWL_RX_MAX_LEN);
} else {
g_rx_msg.len = (pack.pdu_layout.ff.pci.ff_dl_lo8) + ((pack.pdu_layout.ff.pci.ff_dl_hi4) << 8); /*更新SDU数据长度场*/
if (g_rx_msg.data != NULL) free(g_rx_msg.data);
g_rx_msg.data = malloc(g_rx_msg.len);
if (g_rx_msg.data == NULL) {
LOGE("malloc fail.size:%u", g_rx_msg.len);
break;
}
memcpy(g_rx_msg.data, pack.pdu_layout.ff.data, 6);
g_rx_msg.pos = 6;
g_rx_msg.sn = 1;
if (pack.pdu_layout.sf.id == NWL_PHY_ADDR) // 必须为物理寻址
g_rx_msg.ta_type = DIAG_PHYS_REQ;
pack_tx.pdu_layout.fc.pci.fs = NWL_FS_CTS; // 通知发送方继续发送
}
pack_tx.pdu_layout.fc.pci.type = NWL_FLOW_CONTRL;
pack_tx.pdu_layout.fc.pci.bs = NWL_BS_SIZE;
pack_tx.pdu_layout.fc.pci.stmin = NWL_ST_MIN;
pack_tx.can_layout.dlc = 3;
memset(&pack_tx.can_layout.data[3], 0xAA, 5);
app_can_enqueue_msg(CAN_MSG_EVENT_SEND, NWL_RES_ADDR, pack_tx.can_layout.data, 8);
//nwl.tmr_ar = 0; // 启动Ar定时器
LOGD("FIRST_FRAME");
break;
}
case NWL_CONSECUTIVE_FRAME: {
if (pack.pdu_layout.cf.pci.sn == g_rx_msg.sn) {
uint16_t remain_len = g_rx_msg.len - g_rx_msg.pos; // 剩余字节
// 期望的DLC长度
if(((remain_len < 8) && (pack.pdu_layout.cf.dlc >= (remain_len + 1))) ||
((remain_len >= 8) && (pack.pdu_layout.cf.dlc == 8))) {
// 复制数据到SDU
if (remain_len < 8) {
memcpy(g_rx_msg.data + g_rx_msg.pos, pack.pdu_layout.cf.data, remain_len);
g_rx_msg.pos += remain_len;
} else {
memcpy(g_rx_msg.data + g_rx_msg.pos, pack.pdu_layout.cf.data, 7);
g_rx_msg.pos += 7;
}
if (g_rx_msg.pos >= g_rx_msg.len) { // 接收完成
diag_main_send_signal(SIGNAL_DIAG_CF_DATA_DISPOSE);
} else { // 继续组包
// nwl.tmr_cr=0;/*复位Cr定时器*/
}
g_rx_msg.sn++;
}
} else {
LOGE("err sn, %u!=%u.", pack.pdu_layout.cf.pci.sn, g_rx_msg.sn);
send_nrc_data(g_rx_msg.data[1], NRC_REQUEST_SEQUENCE_ERROR);
if (g_rx_msg.data != NULL) {
free(g_rx_msg.data);
g_rx_msg.data = NULL;
}
g_rx_msg.ta_type = 0;
g_rx_msg.sn = 0;
}
if (pack.pdu_layout.cf.pci.sn == 1) {
LOGD("CONSECUTIVE_FRAME");
}
break;
}
case NWL_FLOW_CONTRL: { // 流控帧
if (pack.pdu_layout.fc.dlc >= 3) {
if (pack.pdu_layout.fc.pci.fs == NWL_FS_CTS) { // 请求发送方继续发送CF
diag_main_send_signal(SIGNAL_DIAG_SEND_DATA);
} else if (pack.pdu_layout.fc.pci.fs == NWL_FS_WT) { // 不做处理 不支持
LOGE("Invalid WT");
break;
} else if (pack.pdu_layout.fc.pci.fs == NWL_FS_OVFLW) { // FF_DL超出接收方BUFF大小
} else { // 无效FS
LOGE("Invalid FS");
break;
}
}
LOGD("FLOW_CONTRL:%u", pack.pdu_layout.fc.pci.fs);
break;
}
default:
break;
}
}
/** * @brief 发送否定码数据帧
* @param diag_num: 服务号
* @param nrc: 否定码
*/
void send_nrc_data(uint8_t diag_num, uint8_t nrc) {
uint8_t buff[8] = {0};
buff[0] = 0x03; // 高4bit-帧类型, 低4bit-长度
buff[1] = DIAG_NEG_RES_SID;
buff[2] = diag_num;
buff[3] = nrc;
uint8_t ret = app_can_enqueue_msg(CAN_MSG_EVENT_SEND, NWL_RES_ADDR, buff, 8);
if (ret != 0) {
LOGE("enqueue fail.%u", ret);
}
g_p2_service_time_remaining = 0; // 如果发送诊断报文,则清除倒计时。P2_SERVER_MAX
}
#if AUTOSAR_DIAG_SWITCH
SERVE_INITCALL("diag_main", diag_main_init);
OS_INITCALL("diag_main_os", diag_main_init_os);
#endif
static int sys_reset(atcmd_pack_t *pack) {
diag_main_send_signal(SIGNAL_DIAG_RESET);
return 0;
}
ATCMD_INIT("AT+RESET", sys_reset);
(2)diag_main.h
/******************************************************************************** * @file diag_main.c
* @author jianqiang.xue
* @version V1.0.0
* @date 2023-05-18
* @brief 诊断主函数
********************************************************************************/
#ifndef DIAG_MAIN_H_
#define DIAG_MAIN_H_
/* Includes ------------------------------------------------------------------*/
#include <stdint.h>
#include "app_can.h"
#include "diag_nwl.h"
/* Public define -------------------------------------------------------------*/
#define DIAG_KV_BASE_ADDR (0x32)
#define KV_ID_FRIGER_PRINT (DIAG_KV_BASE_ADDR + 0)
#define SIGNAL_DIAG_CF_DATA_DISPOSE (1 << 0) // 处理诊断接收连续帧数据
#define SIGNAL_DIAG_SEND_DATA (1 << 1) // 处理诊断发送数据(一般是连续帧)
#define SIGNAL_DIAG_SF_DATA_DISPOSE (1 << 2) // 处理诊断接收单帧数据
#define SIGNAL_DIAG_DTC_EN (1 << 3) // DTC 使能
#define SIGNAL_DIAG_DTC_DIS (1 << 4) // DTC 关闭
#define SIGNAL_DIAG_DTC_CLS (1 << 5) // DTC 清除全部
#define SIGNAL_DIAG_DTC_SAVE (1 << 5) // DTC 保存全部
#define SIGNAL_DIAG_RESET (1 << 6) // UDS$11, 软硬件复位
#define SIGNAL_P2_SERVICE_CHECK (1 << 7) // p2服务定时器的回调
#define SIGNAL_P3_SESSION_CHECK (1 << 8) // p3会话定时器的回调
#define SIGNAL_P3_SESSION_START (1 << 9) // p3会话定时器启动
// 服务预处理使能开关
#define DESC_ENABLE_PREHANDLER_USAGE
// 服务后处理使能开关
#define DESC_ENABLE_POSTHANDLER_USAGE
#define UDS_INVALID_SERVICE_INDEX NUMBER_OF_SERVICE_SUPPORTED
/*根据诊断规范否定响应定义*/
#define NRC_GENERAL_REJECT 0x10 // 表示请求的诊断服务被服务器(ECU)拒绝,但在本表中所有已定义的编码都不适用,这时回复此编码。
#define NRC_SERVICE_NOT_SUPPORTED 0x11 // 诊断服务不支持
#define NRC_SUBFUNCTION_NOT_SUPPORTED 0x12 // 子功能不支持
#define NRC_INCORRECT_MESSAGE_LENTH 0x13 // 报文长度不正确
#define NRC_RESPONSE_TOOLONG 0x14 // 服务器准备给出的诊断响应中所包含的数据长度超出了服务器所支持的最大长度时,回复编码。
#define NRC_BUSY_REPEAT_REQUEST 0x21 // 表示服务器忙于执行已请求的诊断服务,暂时无法执行当前请求的诊断服务。
#define NRC_CONDITION_NOT_CORRECT 0x22 // 服务执行条件不满足
#define NRC_REQUEST_SEQUENCE_ERROR 0x24 // 请求服务的顺序不正确
#define NRC_REQUEST_OUT_OF_RANGE 0x31 // 诊断请求中的参数超出定义的范围,或者访问的数据标识符(DID)、例程标识符(RoutinelD)是服务器不支持或在当前会话不支持
#define NRC_ACCESS_DENIED 0x33 // 通常在所请求的诊断服务需要服务器处于解锁状态,但服务器未被解锁时,回复此编码。
#define NRC_INVALID_KEY 0x35 // 服务器收到的安全访问服务请求子功能为发送秘钥(Send Key),但服务器收到的秘钥 (Key)不正确时,回复此编码。
#define NRC_EXCEED_NUM_OF_ATTEMPTS 0x36 // 请求安全访问服务的失败次数超过服务器允许的最大次数时,回复此编码。
#define NRC_TIME_DELAY_NOT_EXPIRED 0x37 // 服务器在安全访问延迟时间内收到安全访问服务请求时,回复此编码。
#define NRC_UPLOAD_DOWNLOAD_NOT_ACCEPTED 0x70 // 由于故障导致从服务器的存储器上传数据失败或向服务器的存储器下载数据失败时,回复此代码。
#define NRC_TRANSFER_DATA_SUSPENDED 0x71 // 由于故障导致数据传输操作被中断时,回复此编码。
#define NRC_GENERAL_PROGRAMMING_FAILURE 0x72 // 服务器在擦除或写入Flash出现错误时,回复此代码。
#define NRC_WRONG_BLOCK_SEQUENCE_COUNTER 0x73 // 在执行数据传输服务(Transfer Data(0x36) service)的过程中,检测到数据块序列编号错误时,回复此编码。
#define NRC_RESPONSE_PENDING 0x78 // 收到诊断请求,等待响应
#define NRC_SUBFUNCTION_NOT_SUPPORTED_INACTIVE_SESSION 0x7E // 诊断请求中服务的子功能参数在当前的会话下不支持时,回复此编码。需要注意的是,回复此编码时,子功能参数是服务器在其它会话下支持的,只是在当前的会话下不支持。如果服务器在任何会话下都不支持此子功能参数,则需回复0x12.
#define NRC_SERVICE_NOT_SUPPORTED_INACTIVE_SESSION 0x7F // 诊断服务在当前会话下不支持。在其它会话下支持的,只是在当前的会话下不支持。如果服务器在任何会话下都不支持此子功能参数,则需回复0x11。
#define NRC_ENGINELS_RUNNING 0x83 // 发动机处于停机状态,而此时发动机处于运转状态,则回复此编码。
#define NRC_ENGINELS_NOT_RUNNING 0x84 // 发动机处于运转状态,而此时发动机处于停机状态,则回复此编码。
#define NRC_ENGINE_RUN_TIME_TOO_LOW 0x85 // 发动机运转的时间超过某一限值,而此时该条件不满足,则回复此编码。
#define NRC_VOLTAGE_TOO_LOW 0x92 // 蓄电池电压低于设定的限值,而此时该条件不满足,则回复此编码。
#define NRC_VOLTAGE_TOO_HIGH 0x93 // 蓄电池电压高于设定的限值,而此时该条件不满足,则回复此编码。
// 无效的消极应答码
#define NRC_NONE 0x00
// 无效的服务下标
#define DIAG_NEG_RES_SID 0x7F
// 否定响应服务数据长度
#define DIAG_NEG_RES_LEN 0x03
// 肯定响应SID偏移量
#define DIAG_POS_RES_ID_OFFSET (1 << 6)
typedef enum { // 会话模式
DEFAULT_SESSION = (1 << 0), // 默认会话
PROGRAMMING_SESSION = (1 << 1), // 编程会话
EXTENDED_SESSION = (1 << 2), // 扩展会话
SUPPLIER_SESSION = (1 << 3), // 供应商会话
} session_t;
typedef enum { // 安全等级
DIAG_SECURITY_LEVEL_1 = (1 << 1), // 安全等级1
DIAG_SECURITY_LEVEL_2 = (1 << 2), // 安全等级2
DIAG_SECURITY_LEVEL_3 = (1 << 3), // 安全等级3
DIAG_NO_SECURITY_LEVEL = (1 << 7), // 无安全等级
} diag_security_t;
/*诊断服务层定时参数配置单位ms*/
#define SRV_DESC_TMR_BASE 1 // 诊断服务层时间基准
#define S3_SERVER_MAX 5000 // 非默认会话保持时间
#define P2_SERVER_MAX 50 // 服务应答时间
#define P2_SERVER_EXTEND_MAX 2000 // 服务应答扩展时间
// 注册诊断服务
#define DIAG_SERVICE_REG(num, security_level, spport_session, spport_addr, pre_func, post_func, main_func) \
static const diag_service_ins_t g_diag_service_##num __attribute__((used, section("diag_srvc"))) = { \
num, security_level, spport_session, spport_addr, pre_func, post_func, main_func}
/* Public enum ---------------------------------------------------------------*/
typedef enum {
RESULT_OK,
RESULT_ERROR, // 错误
RESULT_OUT_OF_RANGE, // 超出范围
RESULT_REQ_SEQUENCE_ERR,
RESULT_REQ_SN_ERR, // 文件接收完毕,仍有数据下发
RESULT_REQ_ERASE_ERR, // 擦除错误
RESULT_BLOCK_SEQUENCE_ERR
} file_check_result_t;
typedef enum {
FILE_GET_ERASE_PROG_CODE_INDEX, // 16字节文件信息
FILE_APP_INDEX // 文件数据
} file_data_t;
typedef enum {
FILE_TYPE_2640 = 0, // ANT
FILE_TYPE_S9 = 2, // MCU
FILE_TYPE_2642 = 4, // BLE
FILE_TYPE_SE = 6, // SE升级数据
FILE_TYPE_SE_COS = 8, // SE个性化数据
FILE_TYPE_FLASH_DRIVER = 0xF, // MCU FlashDeiver
} FILE_TYPE_T;
/* Public typedef ------------------------------------------------------------*/
/*诊断服务预处理函数指针类型*/
typedef void (*pre_cb_t)(void);
/*诊断服务后处理函数指针类型*/
typedef void (*post_cb_t)(uint8_t);
/*诊断服务主处理函数指针类型*/
typedef void (*main_cb_t)(nwl_msg_t*);
typedef struct __attribute__((packed, aligned(1))) { // 诊断服务实体数据类型(用于注册诊断服务)
uint8_t diag_num; // 诊断服务号
uint8_t security_level; // 服务需求的安全等级
uint8_t spport_session; // 服务支持的会话类型
uint8_t spport_addr_t; // 服务支持的寻址类型
pre_cb_t pre_func; // 服务预处理函数
post_cb_t post_func; // 服务后处理函数
main_cb_t main_func; // 服务主处理函数
} diag_service_ins_t;
typedef struct __attribute__((packed, aligned(1))) { // 诊断模式信息(会话模式、安全等级)
session_t session; // 当前会话模式
diag_security_t security_level; // 当前安全等级
} diag_info_t;
typedef struct __attribute__((packed, aligned(1))) { // 文件中的模块信息。由于文件里面地址不连续,所以跳段发送时,需要通过$34,更新模块信息。[ECU刷写是按模块发送]
uint8_t data_format; // 数据格式相关的信息,比如数据是否有压缩,是否有加密,用的什么算法加密等,由主机厂与供应商约定好,用哪个bit来表示压缩、加密等信息。
uint8_t addr_format; // 该参数是代表后续的两个部分mem_addr和mem_size所占的字节长度。addr_format的低4bit表示mem_addr,高4bit表示mem_size。
uint32_t mem_addr; // 开始下载数据的起始位置的地址
uint32_t mem_size; // 这个参数用来把传输数据和内存进行对比,这种操作增加了下载数据的安全性。
uint16_t sn; // 当前包顺序号
uint32_t pos; // 当前接收到文件大小
file_data_t data_type; // 0--16字节文件信息 1--文件数据
} file_info_t;
typedef struct __attribute__((packed, aligned(1))) { // DFU状态信息
uint8_t state; // 0--空闲 1--下载中
// 下面信息由16字节得到
FILE_TYPE_T file_type;
uint32_t file_addr;
uint32_t file_len;
uint32_t file_pos; // 当前接收到文件大小
// end
uint8_t wait_erasure; // 0--无 1--等待擦除(等16字节文件下发) 2--擦除完毕
uint32_t crc32; // 文件crc32动态计算值
uint8_t step; // 0--无 1--$34 2--$36 3--$37
uint8_t receive_end; // 0--接收数据中 1--接收完毕
} dfu_info_t;
/* Public variable -----------------------------------------------------------*/
extern nwl_msg_t g_tx_msg;
extern diag_info_t g_diag_info;
extern uint16_t g_p2_service_time_remaining;
/* Public func ---------------------------------------------------------------*/
void diag_main_send_signal(int32_t signals);
void diag_data_dispose(can_id_t id, uint8_t *data, uint8_t len);
void send_nrc_data(uint8_t diag_num, uint8_t nrc);
#endif
(3)diag_nwl.h
/******************************************************************************** * @file diag_nwl.h
* @author jianqiang.xue
* @version V1.0.0
* @date 2023-05-15
* @brief diag网络层服务头文件
********************************************************************************/
#ifndef DIAG_NWL_H_
#define DIAG_NWL_H_
/* Includes ------------------------------------------------------------------*/
#include <stdint.h>
/* Publib define -------------------------------------------------------------*/
/* Public enum ---------------------------------------------------------------*/
/*网络层协议数据单元帧类型定义*/
typedef enum {
NWL_SINGLE_FRAME = 0, // 单帧SF
NWL_FIRST_FRAME, // 首帧FF
NWL_CONSECUTIVE_FRAME, // 连续帧CF
NWL_FLOW_CONTRL // 流控帧FC
} nwl_frame_st_t;
/*链路层发送PDU状态*/
typedef enum {
NML_D_TX_NOT_START = 0, // 未启动发送
NWL_D_TX_START, // 启动发送
NWL_D_TX_GOING, // 正在发送
NWL_D_TX_FINISHED // 发送完成
} nwl_pdu_tx_st_t;
/*流状态参数定义*/
typedef enum {
NWL_FS_CTS = 0, // 请求发送方继续发送CF
NWL_FS_WT, // 请求发送方等待
NWL_FS_OVFLW // 告知发送方缓存溢出
} nwl_fs_st_t;
typedef enum { // 寻址类型
DIAG_PHYS_REQ = (1 << 1), // 物理地址 0x780
DIAG_FUNC_REQ = (1 << 2), // 功能寻找 0x7DF
} diag_find_addr_t;
/*消息寻址定义*/
typedef enum {
NWL_PHYSICAL, // 物理寻址
NWL_FUNCTIONAL, // 功能寻址
NWL_TXJ_PHYSICAL,
NWL_TXJ_FUNCTIONAL
} nwl_address_st_t;
/*网络层引擎状态定义*/
typedef enum {
NWL_IDLE = 0, // 网络层空闲
NWL_TX_SDU, // 网络层正在发送数据包
NWL_RX_SDU // 网络层正在接收数据包
} nwl_sched_st_t;
/*网络层接收状态定义*/
typedef enum {
NWL_RX_WAIT_SF_OR_FF = 0, // 等待SF或FF接收
NWL_RX_WAIT_PDU_SENT, // 等待PDU发送完成
NWL_RX_WAIT_FC_SENT, // 等待FC发送完成
NWL_RX_WAIT_CF // 等待CF接收
} nwl_rx_st_t;
/*网络层发送状态定义*/
typedef enum {
NWL_TX_SF_OR_FF = 0, // 发送SF或FF
NWL_TX_WAIT_PDU_SENT, // 等待PDU发送完成
NWL_TX_WAIT_FF_SENT, // 等待FF发送完成
NWL_TX_WAIT_FC, // 等待FC接收
NWL_TX_SEND_CF, // 发送CF
NWL_TX_WAIT_CF_SENT // 等待CF发送完成
} nwl_tx_st_t;
/*网络层服务结果定义*/
typedef enum {
NWL_ERROR = 0, /*无法描述的错误*/
NWL_OK, /*正常*/
NWL_TIMEOUT_A, /*Ar或As超时,链路层发送PDU超时*/
NWL_TIMEOUT_BS, /*Bs超时,发送方等待FC超时*/
NWL_TIMEOUT_CR, /*Cr超时,接收方等待CF超时*/
NWL_WRONG_SN, /*SN错误,接收方收到错误的SN*/
NWL_INVALID_FS, /*FS错误,发送方收到错误的FS*/
NWL_UNEXP_PDU, /*收到不期望的PDU*/
NWL_WFT_OVRN, /*发送方收到WAIT帧超出规定*/
NWL_BUFFER_OVFLW /*发送方请求的发送数据超过接收方BUFF大小*/
} nwl_result_st_t;
/* Public typedef ------------------------------------------------------------*/
/*网络层协议数据单元数据类型*/
typedef union {
struct {
uint32_t id;
uint8_t data[8];
uint8_t dlc;
} can_layout;
union {
struct { // 首帧
uint32_t id;
struct {
uint8_t ff_dl_hi4 : 4; // s32位域 高位在前 低位在后 cw5.1 位域 低位在前 高位在后
uint8_t type : 4;
uint8_t ff_dl_lo8; // s32位域 高位在前 低位在后 cw5.1 位域 低位在前 高位在后
} pci;
uint8_t data[6];
uint8_t dlc;
} ff;
struct { // 单帧
uint32_t id;
struct {
uint8_t sf_dl : 4; // s32位域 高位在前 低位在后 cw5.1 位域 低位在前 高位在后
uint8_t type : 4;
} pci;
uint8_t data[7];
uint8_t dlc;
} sf;
struct { // 连续帧
uint32_t id;
struct {
uint8_t sn : 4;
uint8_t type : 4;
} pci;
uint8_t data[7];
uint8_t dlc;
} cf;
struct { // 流控帧
uint32_t id;
struct {
uint8_t fs : 4;
uint8_t type : 4;
uint8_t bs;
uint8_t stmin;
} pci;
uint8_t data[5];
uint8_t dlc;
} fc;
} pdu_layout;
struct {
uint8_t : 8;
uint8_t : 8;
uint8_t : 8;
uint8_t : 8;
uint8_t : 4;
uint8_t type : 4;
uint8_t : 8;
uint8_t : 8;
uint8_t : 8;
uint8_t : 8;
uint8_t : 8;
uint8_t : 8;
uint8_t : 8;
uint8_t : 8;
} pci;
} nwl_pdu_t;
/*网络层数据包数据类型*/
typedef struct __attribute__((packed)) {
diag_find_addr_t ta_type;
uint8_t* data;
uint16_t len;
// 连续帧 才使用下面变量
uint16_t pos; // 当前数据长度, 连续帧组包使用。
uint8_t sn : 4; // 顺序号
uint8_t reserve : 4;
} nwl_msg_t;
/*网络层服务数据类型*/
/* Public variable -----------------------------------------------------------*/
/* Public func ---------------------------------------------------------------*/
#endif
