4. 编程会话切换
任务目标
实现UDS诊断服务中的会话控制(SessionControl,0x10),使ECU能够从默认会话(Default Session)切换到编程会话(Programming Session,会话ID 0x02),并正确响应肯定/否定应答,同时验证切换后的会话状态。
核心实现
#include <stdint.h>
#include <string.h>
/* UDS会话状态 */
typedef enum {
SESSION_DEFAULT = 0x01,
SESSION_PROGRAMMING = 0x02,
SESSION_EXTENDED = 0x03
} SessionType;
/* 会话控制服务ID */
#define SID_SESSION_CONTROL 0x10
#define SUBFUNC_DEFAULT 0x01
#define SUBFUNC_PROGRAMMING 0x02
/* 应答字节 */
#define RESP_POSITIVE 0x50 /* 0x10 + 0x40 */
#define RESP_NEGATIVE 0x7F
#define NRC_INVALID_SUBFUNC 0x12
#define NRC_CONDITIONS_NOT_CORRECT 0x22
static SessionType current_session = SESSION_DEFAULT;
/* 会话切换处理函数
* 输入: rx_data - 接收到的请求数据(含SID)
* rx_len - 请求长度
* 输出: tx_data - 应答数据缓冲区
* 返回: 应答长度(0表示不应答)
*/
uint8_t SessionControl_Handler(const uint8_t *rx_data, uint8_t rx_len,
uint8_t *tx_data)
{
uint8_t subfunc;
uint8_t resp_len = 0;
/* 最小请求长度检查:SID + SubFunction */
if (rx_len < 2) {
return 0; /* 帧错误,不应答 */
}
subfunc = rx_data[1];
/* 仅支持默认会话和编程会话切换 */
if (subfunc != SUBFUNC_DEFAULT && subfunc != SUBFUNC_PROGRAMMING) {
/* 否定应答:不支持的子功能 */
tx_data[0] = RESP_NEGATIVE;
tx_data[1] = SID_SESSION_CONTROL;
tx_data[2] = NRC_INVALID_SUBFUNC;
return 3;
}
/* 编程会话切换条件检查:
* 仅允许从默认会话切换到编程会话,
* 且编程会话下不允许再切换到编程会话 */
if (subfunc == SUBFUNC_PROGRAMMING) {
if (current_session != SESSION_DEFAULT) {
tx_data[0] = RESP_NEGATIVE;
tx_data[1] = SID_SESSION_CONTROL;
tx_data[2] = NRC_CONDITIONS_NOT_CORRECT;
return 3;
}
current_session = SESSION_PROGRAMMING;
} else {
/* 默认会话切换:任何状态下都允许 */
current_session = SESSION_DEFAULT;
}
/* 肯定应答:0x50 + 子功能 + 当前会话ID */
tx_data[0] = RESP_POSITIVE;
tx_data[1] = subfunc;
tx_data[2] = (uint8_t)current_session;
return 3;
}
/* 获取当前会话状态(用于验证) */
SessionType GetCurrentSession(void)
{
return current_session;
}
运行与验证
测试调用示例:
#include <assert.h>
#include <stdio.h>
int main(void)
{
uint8_t request[2];
uint8_t response[8];
uint8_t resp_len;
/* 测试1:默认会话 -> 编程会话 */
request[0] = SID_SESSION_CONTROL;
request[1] = SUBFUNC_PROGRAMMING;
resp_len = SessionControl_Handler(request, 2, response);
assert(resp_len == 3);
assert(response[0] == RESP_POSITIVE);
assert(response[1] == SUBFUNC_PROGRAMMING);
assert(response[2] == SESSION_PROGRAMMING);
assert(GetCurrentSession() == SESSION_PROGRAMMING);
printf("Test1 PASS: Switch to Programming Session OK\n");
/* 测试2:编程会话 -> 编程会话(应否定) */
resp_len = SessionControl_Handler(request, 2, response);
assert(resp_len == 3);
assert(response[0] == RESP_NEGATIVE);
assert(response[2] == NRC_CONDITIONS_NOT_CORRECT);
printf("Test2 PASS: Re-entry to Programming rejected\n");
/* 测试3:编程会话 -> 默认会话 */
request[1] = SUBFUNC_DEFAULT;
resp_len = SessionControl_Handler(request, 2, response);
assert(resp_len == 3);
assert(response[0] == RESP_POSITIVE);
assert(response[2] == SESSION_DEFAULT);
assert(GetCurrentSession() == SESSION_DEFAULT);
printf("Test3 PASS: Back to Default Session OK\n");
/* 测试4:不支持的子功能(如扩展会话0x03) */
request[1] = 0x03;
resp_len = SessionControl_Handler(request, 2, response);
assert(resp_len == 3);
assert(response[0] == RESP_NEGATIVE);
assert(response[2] == NRC_INVALID_SUBFUNC);
printf("Test4 PASS: Unsupported subfunction rejected\n");
printf("ALL TESTS PASSED\n");
return 0;
}
编译与执行:
gcc -o session_test session_test.c
./session_test
预期输出:
Test1 PASS: Switch to Programming Session OK
Test2 PASS: Re-entry to Programming rejected
Test3 PASS: Back to Default Session OK
Test4 PASS: Unsupported subfunction rejected
ALL TESTS PASSED
验证判定标准
- 肯定应答格式为
0x50 0x02 0x02(SID+0x40、子功能、当前会话ID),表示已进入编程会话; - 从编程会话再次请求切换到编程会话时,返回NRC 0x22(条件不满足);
- 请求不支持的子功能(如0x03扩展会话)时,返回NRC 0x12(子功能不支持);
- 通过
GetCurrentSession()读取的状态与应答中返回的会话ID一致。
集成说明:在实际Bootloader中,此处理函数应挂接到CAN/LIN/UART等物理层接收回调中,在收到完整UDS帧后调用;切换至编程会话后,Bootloader应跳转到编程入口等待0x34(请求下载)等后续服务。