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(请求下载)等后续服务。