基于STM32F103战舰开发版实现的UDS诊断服务,本篇文章主要展示了UDS诊断会话服务的功能。
UDS(Unified Diagnostic Services)协议,即统一的诊断服务,是面向整车所有ECU的一种诊断通信方式,是基于ISO 14229规范的规范化诊断服务标准,其位于OSI模型中的应用层。
1. 26个UDS服务
2. 通讯方式
2.1 request
诊断的request格式分2种,一种有sub-function的,一种没有sub-function的。
当 UDS 服务支持 Subfunction 的请求和响应格式时:
- 请求格式为 :"SID + 一个字节Subfunction + 具体的数据",肯定响应为"SID+40+Subfunction+具体的数据"。
而有的UDS服务里面是不支持 Subfunction,是支持DID的,DID是“数据ID”的意思,
- 请求格式为:“SID+具体的DID+数据内容”,肯定响应为:“SID+40+DID+具体的数据”。
支持Subfunction和DID:
- 请求格式为:“SID + Sub-Function + DID + 数据。
2.2 response
一般来讲,response会在一个服务被request且执行之后发送,成功的话就发positive response,失败的话要发negative response,但是也有例外的时候,比如ECUreset,他要求先发送response,然后再去执行具体的reset,因为如果先reset,那么ECU的通信模块shut down,是无法发送出去response的。一般像这种特殊情况,协议会在描述具体服务时标注出来。
2.2.1 Positive Response:
基本格式:
- <SID+0x4**0> + <Sub-function**> + <Parameter**>**
- <SID+0x4**0> + <Parameter**>
比如session control的service:
- Send:10 01(byte1的10是SID,byte2的01是sub-function,且可知Bit 7是false)
- Receive:50 01 (byte1是SID+0x40,byte2是sub-funtion)
不带sub-function的例子,比如ReadDataById这个service:
- Send:22 F1 86(byte1是SID,byte2和byte3是DID,可视为parameter的一种)
- Receive:62 F1 86 01(byte1的62是SID+0x40,byte2和byte3是DID,byte4是读到的数据)
2.2.2 Negative Response:
基本格式:
- <0x7F**> + <SID**> + <NRC**> //NRC:Negative Response Code(否定响应码)**
只要是Negative Response,第一字节就一定是0x7F,第二字节照抄原来的SID,第三个字节是错误响应码,指示具体错误响应的原因,这个NRC可以在协议中查到,并且不同的服务所支持的NRC也有规定。
拿 session control 这个service来举例:
- Send:10 05(现在sun-function变为05了,假定系统不支持这个sub-function)
- Receive:7F 10 12(7F即指代错误响应,10为SID,12是NRC,查协议可知其指代sub-function not supported 这个错误)
- 常用的UDS个服务
-
4。 核心代码实现
1。10服务
include "SID10_SessionControl.h"
include "service_cfg.h"
include "SID27_SecurityAccess.h"
// 收到服务请求到做出响应之间的最大时间,单位:ms
define P2_SERVER 50
// 回复 NRC 0x78 后到真正的正响应之间的最大时间,单位:ms,接收方接收到这个时间后要 x10
define P2X_SERVER 400
// 诊断会话状态
static uds_session_t uds_session = UDS_SESSION_STD;
/**
- 函数名称: void set_current_session(uds_session_t session)
- 功能说明: 设置当前诊断会话状态
- 输入参数: uds_session_t session --会话状态
- 输出参数: 无
- 函数返回: 无
- 其它说明: 无
**/
void set_current_session(uds_session_t session)
{
uds_session = session;
}
/**
- 函数名称: uds_session_t get_current_session(void)
- 功能说明: 获取当前诊断会话状态
- 输入参数: 无
- 输出参数: 无
- 函数返回: 当前会话状态
- 其它说明: 无
**/
uds_session_t get_current_session(void)
{
return uds_session;
}
/**
-
函数名称: bool_t service_10_check_len(const uint8_t* msg_buf, uint16_t msg_dlc)
-
功能说明: 检查 10 服务数据长度是否合法
-
输入参数: uint16_t msg_dlc --数据长度
-
输出参数: 无
-
函数返回: TRUE: 合法; FALSE: 非法
-
其它说明: 无
**/
bool_t service_10_check_len(const uint8_t* msg_buf, uint16_t msg_dlc)
{
bool_t ret = FALSE;
(void)msg_buf;
if(2 == msg_dlc)
ret = TRUE;
return ret;
}
/**
-
函数名称: void service_10_SessionControl(const uint8_t* msg_buf, uint16_t msg_dlc)
-
功能说明: 10 服务 - 诊断会话控制
-
输入参数: uint8_t* msg_buf --数据首地址
uint8_t msg_dlc --数据长度
-
输出参数: 无
-
函数返回: 无
-
其它说明: 无
**/
void service_10_SessionControl(const uint8_t* msg_buf, uint16_t msg_dlc)
{
uint8_t subfunction = 0;
uint8_t rsp_buf[8];
subfunction = UDS_GET_SUB_FUNCTION(msg_buf[1]);
rsp_buf[0] = USD_GET_POSITIVE_RSP(SID_10);
rsp_buf[1] = subfunction;
rsp_buf[2] = (uint8_t)(P2_SERVER >> 8);
rsp_buf[3] = (uint8_t)(P2_SERVER & 0x00ff);
rsp_buf[4] = (uint8_t)(P2X_SERVER >> 8);
rsp_buf[5] = (uint8_t)(P2X_SERVER & 0x00ff);
switch (subfunction)
{
case UDS_SESSION_STD: // 默认会话
set_current_session((uds_session_t)subfunction);
set_current_sa_lv(UDS_SA_NON);//printf("默认会话\r\n");
uds_positive_rsp(rsp_buf, 6);
break;
case UDS_SESSION_PROG: // 编程会话
set_current_session((uds_session_t)subfunction);
set_current_sa_lv(UDS_SA_NON);//printf("编程会话\r\n");
uds_positive_rsp(rsp_buf, 6);
uds_timer_start(UDS_TIMER_S3server);
break;
case UDS_SESSION_EXT: // 扩展会话
set_current_session((uds_session_t)subfunction);
set_current_sa_lv(UDS_SA_NON);//printf("扩展会话\r\n");
uds_positive_rsp(rsp_buf, 6);
uds_timer_start(UDS_TIMER_S3server);
break;
default:
uds_negative_rsp(SID_10, NRC_SUBFUNCTION_NOT_SUPPORTED);
break;
}
}
/****EOF****/
2。31服务
include "SID31_RoutineControl.h"
include "service_cfg.h"
include "uds_service.h"
/** 31 01 ff 00 擦除内存
- 31 01 02 02 检查传输数据完整性
- 31 01 02 03 更新条件查询
- 31 01 ff 01 软硬件一致性检查
- 31 01 df e0 从节点模式选择
- 31 01 02 04 数字签名
*/
typedef enum __UDS_ROUTINE_CTRL_TYPE__
{
UDS_ROUTINE_CTRL_NONE = 0,
UDS_ROUTINE_CTRL_START = 0x01,
UDS_ROUTINE_CTRL_STOP = 0x02,
UDS_ROUTINE_CTRL_REQUEST_RESULT = 0x03
}uds_routine_ctrl_type;
/**
-
函数名称: bool_t service_31_check_len(const uint8_t* msg_buf, uint16_t msg_dlc)
-
功能说明: 检查 31 服务数据长度是否合法
-
输入参数: uint16_t msg_dlc --数据长度
-
输出参数: 无
-
函数返回: TRUE: 合法; FALSE: 非法
-
其它说明: 无
**/
bool_t service_31_check_len(const uint8_t* msg_buf, uint16_t msg_dlc)
{
bool_t ret = FALSE;
(void)msg_buf;
if(msg_dlc > 4)
ret = TRUE;
return ret;
}
/**
-
函数名称: void service_31_RoutineControl(const uint8_t* msg_buf, uint16_t msg_dlc)
-
功能说明: 31 服务 - 例程控制
-
输入参数: uint8_t* msg_buf --数据首地址
uint8_t msg_dlc --数据长度
-
输出参数: 无
-
函数返回: 无
-
其它说明: 无
**/
void service_31_RoutineControl(const uint8_t* msg_buf, uint16_t msg_dlc)
{
uint8_t subfunction;
uint8_t rsp_buf[8];
uint16_t rid;
// uint16_t rid_n;
bool_t find_rid;
subfunction = UDS_GET_SUB_FUNCTION (msg_buf[1]);
rid = ((uint16_t)msg_buf[2]) << 8;
rid |= msg_buf[3];
find_rid = FALSE;
// for (rid_n = 0; rid_n < RTCTRL_NUM; rid_n++)
// {
// if (rtctrl_list[rid_n].rid == rid)
// {
// find_rid = TRUE;
// break;
// }
// }
rsp_buf[0] = USD_GET_POSITIVE_RSP(SID_31);
rsp_buf[1] = msg_buf[1];
rsp_buf[2] = msg_buf[2];
rsp_buf[3] = msg_buf[3];
switch (subfunction)
{
case UDS_ROUTINE_CTRL_START:
if (find_rid == TRUE)
{
// if (rtctrl_list[rid_n].rtst == UDS_RT_ST_RUNNING)
if(1)
{
uds_negative_rsp (SID_31,NRC_REQUEST_SEQUENCE_ERROR);
}
else
{
// rtctrl_list[rid_n].init_routine ();
uds_positive_rsp (rsp_buf,4);
}
}
else
{
uds_negative_rsp (SID_31,NRC_REQUEST_OUT_OF_RANGE);
}
break;
case UDS_ROUTINE_CTRL_STOP:
if (find_rid == TRUE)
{
// if (rtctrl_list[rid_n].rtst == UDS_RT_ST_IDLE)
if(1)
{
uds_negative_rsp (SID_31,NRC_REQUEST_SEQUENCE_ERROR);
}
else
{
// rtctrl_list[rid_n].stop_routine ();
uds_positive_rsp (rsp_buf,4);
}
}
else
{
uds_negative_rsp (SID_31,NRC_REQUEST_OUT_OF_RANGE);
}
break;
case UDS_ROUTINE_CTRL_REQUEST_RESULT:
if (find_rid == TRUE)
{
// if (rtctrl_list[rid_n].rtst == UDS_RT_ST_IDLE)
if(1)
{
uds_negative_rsp (SID_31,NRC_REQUEST_SEQUENCE_ERROR);
}
else
{
// rsp_buf[4] = (uint8_t)rtctrl_list[rid_n].rtst;
}
}
else
{
uds_negative_rsp (SID_31,NRC_REQUEST_OUT_OF_RANGE);
}
break;
default:
uds_negative_rsp (SID_31, NRC_SUBFUNCTION_NOT_SUPPORTED);
break;
}
}
/****EOF****/
3。85服务
include "SID85_ControlDTCSetting.h"
include "service_cfg.h"
include "uds_service.h"
bool_t dtc_setting = UDS_DTC_SETTING_ON;
/**
-
函数名称: bool_t service_85_check_len(const uint8_t* msg_buf, uint16_t msg_dlc)
-
功能说明: 检查 85 服务数据长度是否合法
-
输入参数: uint16_t msg_dlc --数据长度
-
输出参数: 无
-
函数返回: TRUE: 合法; FALSE: 非法
-
其它说明: 无
**/
bool_t service_85_check_len(const uint8_t* msg_buf, uint16_t msg_dlc)
{
bool_t ret = FALSE;
(void)msg_buf;
if(2 == msg_dlc)
ret = TRUE;
return ret;
}
/**
-
函数名称: void service_85_ControlDTCSetting(const uint8_t* msg_buf, uint16_t msg_dlc)
-
功能说明: 85 服务 - 控制 DTC 设置
-
输入参数: uint8_t* msg_buf --数据首地址
uint8_t msg_dlc --数据长度
-
输出参数: 无
-
函数返回: 无
-
其它说明: 无
**/
void service_85_ControlDTCSetting(const uint8_t* msg_buf, uint16_t msg_dlc)
{
uint8_t subfunction;
uint8_t rsp_buf[8];
subfunction = UDS_GET_SUB_FUNCTION (msg_buf[1]);
switch (subfunction)
{
case UDS_DTC_SETTING_ON:
dtc_setting = UDS_DTC_SETTING_ON;
rsp_buf[0] = USD_GET_POSITIVE_RSP(SID_85);
rsp_buf[1] = subfunction;
uds_positive_rsp (rsp_buf,2);
break;
case UDS_DTC_SETTING_OFF:
dtc_setting = UDS_DTC_SETTING_OFF;
rsp_buf[0] = USD_GET_POSITIVE_RSP(SID_85);
rsp_buf[1] = subfunction;
uds_positive_rsp (rsp_buf,2);
break;
default:
uds_negative_rsp (SID_85,NRC_SUBFUNCTION_NOT_SUPPORTED);
break;
}
}
/****EOF****/
4。3E服务
include "SID3E_TesterPresent.h"
include "service_cfg.h"
include "uds_service.h"
define ZERO_SUBFUNCTION (0x00)
/**
-
函数名称: bool_t service_3E_check_len(const uint8_t* msg_buf, uint16_t msg_dlc)
-
功能说明: 检查 3E 服务数据长度是否合法
-
输入参数: uint16_t msg_dlc --数据长度
-
输出参数: 无
-
函数返回: TRUE: 合法; FALSE: 非法
-
其它说明: 无
**/
bool_t service_3E_check_len(const uint8_t* msg_buf, uint16_t msg_dlc)
{
bool_t ret = FALSE;
(void)msg_buf;
if(2 == msg_dlc)
ret = TRUE;
return ret;
}
/**
-
函数名称: void service_3E_TesterPresent(const uint8_t* msg_buf, uint16_t msg_dlc)
-
功能说明: 3E 服务 - 待机握手
-
输入参数: uint8_t* msg_buf --数据首地址
uint8_t msg_dlc --数据长度
-
输出参数: 无
-
函数返回: 无
-
其它说明: 无
**/
void service_3E_TesterPresent(const uint8_t* msg_buf, uint16_t msg_dlc)
{
uint8_t subfunction;
uint8_t rsp_buf[8];
subfunction = UDS_GET_SUB_FUNCTION (msg_buf[1]);
if (subfunction == ZERO_SUBFUNCTION)
{
rsp_buf[0] = USD_GET_POSITIVE_RSP(SID_3E);
rsp_buf[1] = subfunction;
uds_positive_rsp (rsp_buf,2);
}
else
{
uds_negative_rsp(SID_3E, NRC_SUBFUNCTION_NOT_SUPPORTED);
}
}
/****EOF****/
5。28服务
include "SID28_CommunicationControl.h"
include "service_cfg.h"
include "uds_service.h"
/ uds Communication control type /
typedef enum UDS_CC_TYPE
{
UDS_CC_TYPE_NONE = 0,
UDS_CC_TYPE_NORMAL,
UDS_CC_TYPE_NM,
UDS_CC_TYPE_NM_NOR
}uds_cc_type;
bool_t dis_normal_xmit; // 禁止发送标志
bool_t dis_normal_recv; // 禁止接收标志
/**
-
函数名称: bool_t service_28_check_len(const uint8_t* msg_buf, uint16_t msg_dlc)
-
功能说明: 检查 28 服务数据长度是否合法
-
输入参数: uint16_t msg_dlc --数据长度
-
输出参数: 无
-
函数返回: TRUE: 合法; FALSE: 非法
-
其它说明: 无
**/
bool_t service_28_check_len(const uint8_t* msg_buf, uint16_t msg_dlc)
{
bool_t ret = FALSE;
(void)msg_buf;
if(3 == msg_dlc)
ret = TRUE;
return ret;
}
/**
-
函数名称: void service_28_CommunicationControl(const uint8_t* msg_buf, uint16_t msg_dlc)
-
功能说明: 28 服务 - 通讯控制
-
输入参数: uint8_t* msg_buf --数据首地址
uint8_t msg_dlc --数据长度
-
输出参数: 无
-
函数返回: 无
-
其它说明: 无
**/
void service_28_CommunicationControl(const uint8_t* msg_buf, uint16_t msg_dlc)
{
uint8_t subfunction;
uint8_t rsp_buf[8];
uint8_t cc_type;
subfunction = UDS_GET_SUB_FUNCTION(msg_buf[1]);
cc_type = msg_buf[2];
switch (subfunction)
{
case UDS_CC_MODE_RX_TX: // 使能接收和发送
if (cc_type == UDS_CC_TYPE_NORMAL || cc_type == UDS_CC_TYPE_NM || cc_type == UDS_CC_TYPE_NM_NOR)
{
dis_normal_xmit = FALSE;
dis_normal_recv = FALSE;
rsp_buf[0] = USD_GET_POSITIVE_RSP(SID_28);
rsp_buf[1] = subfunction;
uds_positive_rsp(rsp_buf, 2);
}
else
{
uds_negative_rsp(SID_28, NRC_REQUEST_OUT_OF_RANGE);
}
break;
case UDS_CC_MODE_NO_NO: // 禁止接收、禁止发送
if (cc_type == UDS_CC_TYPE_NORMAL || cc_type == UDS_CC_TYPE_NM || cc_type == UDS_CC_TYPE_NM_NOR)
{
dis_normal_xmit = TRUE;
dis_normal_recv = TRUE;
rsp_buf[0] = USD_GET_POSITIVE_RSP(SID_28);
rsp_buf[1] = subfunction;
uds_positive_rsp(rsp_buf, 2);
}
else
{
uds_negative_rsp(SID_28, NRC_REQUEST_OUT_OF_RANGE);
}
break;
default:
uds_negative_rsp(SID_28, NRC_SUBFUNCTION_NOT_SUPPORTED);
break;
}
}
/****EOF****/
主函数
-
比如当我们想向外发送 100 个字节数据的时候,由于物理硬件的限制(CAN 一帧最多只能发送 8 个字节的数据),我们是没用办法把这 100 个字节的数据一次性全部发送出去的,这时候我们不得不将一整包数据拆分,然后一帧一帧地发送出去,"uds_tp.c" 就是实现这个拆分功能的,比如给每一帧添加一个帧序号等等,这样子接收方才能够准确的将接收到的数据完整的还原成一整包数据;有发送就有接收,"uds_tp.c" 还能将接收到的多帧数据拼接组装,去除帧序号等辅助信息,最后形成一整包完整的有效数据,再将其传送到上层或应用层去处理。"uds_tp.c" 的实现是完全遵循 ISO 15765-2 协议的
-
-
经 "uds_tp.c" 处理过的数据将继续交由 "uds_service.c" 处理,其实 "UDSLogic" 目录下的所有源码从层次划分来讲都应该是属于 "uds_service.c" 的,然而我们还是拆分出来 "UDSLogic",其原因有两个,一是如果代码都写到 "uds_service.c" 文件中,那么这个文件就会变得过于臃肿,代码阅读起来就不是那么容易了;二是我们想将业务无关代码部分拆分出来,"UDSBase" 下的代码属于业务无关代码,使用者甚至完全不需要理解这部分代码是如何实现的,"UDSLogic" 目录下的代码属于业务相关代码,针对不同的业务需求这里面的代码可能是需要变动的,为了能够更快速的定位代码,我们也根据服务 ID 号做了功能拆分,每个服务单独对应一个 .c 文件,即便是不打开文件的情况下我们也可以根据文件名称快速定位到需要的位置
-
物理硬件的收发对接完成后并不是意味着协议栈就已经能正常工作了,我们还需要将 uds_1ms_task() 函数放到 1ms 定时器中周期调用,除此之外不能忘了调用 uds_init() 对诊断协议栈进行初始化
最后,我们需要根据实际业务需求更改下面 3 个宏
define REQUEST_ID 0x123 // 请求 ID
define FUNCTION_ID 0x7DF // 功能 ID
define RESPONSE_ID 0x456 // 应答 ID
这套协议栈不光能适配 CAN 总线,想要适配 LIN 总线只需要将 FRAME_SIZE 宏改为 7 即可;想要适配 CANFD 总线只需要将 FRAME_SIZE 宏改为 64 即可
如果需要新增服务处理,则首先在 "service_cfg.c" 中添加配置,然后再实现具体的服务处理代码,注意这部分代码的实现要遵循 ISO 14229 协议