从零实现可移植的 CCP 从站:CRO/CRM、MTA 与 DAQ 样例
本文最后更新于8 天前,其中的信息可能已经过时,如有错误请留言评论。

上一篇《CCP 原理与 ECU 在线标定:从 CRO/DTO 到 DAQ 和标定页》已经解释了协议角色、MTA、DAQ 和标定页的工作模型。本文不再铺陈原理,而是回答更工程化的问题:怎样写出一个可移植、边界安全、能真正接到 CAN 驱动和周期任务上的 CCP 从站骨架。

声明:下文是原创的教学用 C99 样例,只实现 CCP 2.1 的一个明确子集,不是完整量产协议栈,也不是从任何私有项目直接复制。文中的 Station Address、CAN ID、虚拟地址和内存布局均为示例值;不包含项目代号、客户信息、真实 ECU 标识、私有 Seed&Key 常数或量产源码。

一、样例范围与非目标

能力本样例覆盖边界
会话CONNECTTESTDISCONNECTEXCHANGE_ID单会话、首个有效 CONNECT 绑定 CAN 通道
内存访问SET_MTAUPLOADSHORT_UPLOADDNLOADDNLOAD6只访问应用显式注册的虚拟地址白名单
DAQGET_DAQ_SIZESET_DAQ_PTRWRITE_DAQSTART_STOPSTART_STOP_ALL静态上限;每个经典 CAN DTO 最多 7 字节数据
调度10 ms / 100 ms Event Channel、Prescaler事件由应用任务显式调用,不在协议内创建定时器
发送count 的 DTO/CRM 环形队列、txBusyTxConfirmation队列满记录 overrun,不覆盖未发送帧
标定页页面选择/读取回调契约核心不绑定 OVC、MPU 或具体芯片寄存器
扩展能力Seed&Key、Flash Programming、Checksum 的命令扩展接口仅提供拒绝默认值和接入点,不伪装成已实现

样例采用 8 字节经典 CAN 帧、Little-endian 命令字段和单个活动会话。若上位机/A2L 使用不同字节序、DAQ PID 规划或地址扩展语义,应在配置层明确统一,而不是在收包时猜测。

二、分层与依赖方向

Application / A2L configuration
  |  MemoryRegion, Event Channel, calibration-page callbacks
  v
CCP protocol core
  |  CRO dispatch, CRM, MTA, DAQ List -> ODT -> Entry, timeout
  v
CAN transport adapter
  |  CRO CAN ID filtering, asynchronous send, TxConfirmation
  v
CAN driver / ISR
  • CCP 协议核心:只处理协议状态、命令字段、CRM、MTA、DAQ 和发送队列,不直接访问 CAN 寄存器。
  • CAN 传输适配:过滤示例 CRO CAN ID,把 8 字节帧交给核心;发送完成后回调确认。
  • 地址白名单/内存映射:把“协议虚拟地址”映射到已注册缓冲区,并校验权限和完整访问区间。
  • 周期事件入口:10 ms、100 ms 任务只调用统一的 Ccp_Event,Prescaler 在协议上下文内计数。
  • 应用与 A2L 配置:双方共同约定虚拟地址、数据类型、换算、Event Channel 与 PID;固件不信任主站传入的裸指针。

三、完整、连续的 C99 样例

下面是一份可单文件编译的教学实现。代码先定义公共类型与配置,再实现核心,最后给出最小 CAN 适配和 main() 调用。真实工程可以按已有模块边界拆成 ccp.c/.h、传输适配和应用配置,但不需要为了文件行数再增加只转发的类或层。

/*
 * Portable CCP 2.1 teaching subset (C99).
 * All CAN identifiers, station addresses and virtual addresses are examples.
 * This code is original teaching material, not copied from a private project.
 */
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#define CCP_FRAME_SIZE              8u
#define CCP_CRM_PAYLOAD_MAX         5u
#define CCP_CRO_DNLOAD_MAX          5u
#define CCP_CRO_DNLOAD6_SIZE        6u
#define CCP_MAX_DAQ_LISTS           2u
#define CCP_MAX_ODTS_PER_LIST       4u
#define CCP_MAX_ENTRIES_PER_ODT     7u
#define CCP_DTO_DATA_MAX            7u
#define CCP_TX_QUEUE_DEPTH         16u
#define CCP_EVENT_CHANNEL_COUNT     2u
#define CCP_EVENT_10MS              0u
#define CCP_EVENT_100MS             1u
#define CCP_SESSION_TIMEOUT_MS  10000u
#define CCP_NO_CHANNEL            0xFFu

#define CCP_MEM_READ              0x01u
#define CCP_MEM_WRITE             0x02u

#define CCP_CMD_CONNECT           0x01u
#define CCP_CMD_SET_MTA           0x02u
#define CCP_CMD_DNLOAD            0x03u
#define CCP_CMD_UPLOAD            0x04u
#define CCP_CMD_TEST              0x05u
#define CCP_CMD_START_STOP        0x06u
#define CCP_CMD_DISCONNECT        0x07u
#define CCP_CMD_START_STOP_ALL    0x08u
#define CCP_CMD_BUILD_CHKSUM      0x0Eu
#define CCP_CMD_SHORT_UPLOAD      0x0Fu
#define CCP_CMD_CLEAR_MEMORY      0x10u
#define CCP_CMD_GET_SEED          0x12u
#define CCP_CMD_UNLOCK            0x13u
#define CCP_CMD_GET_DAQ_SIZE      0x14u
#define CCP_CMD_SET_DAQ_PTR       0x15u
#define CCP_CMD_WRITE_DAQ         0x16u
#define CCP_CMD_EXCHANGE_ID       0x17u
#define CCP_CMD_PROGRAM           0x18u
#define CCP_CMD_PROGRAM6          0x22u
#define CCP_CMD_DNLOAD6           0x23u

#define CCP_PID_CRM               0xFFu
#define CCP_ERR_ACK               0x00u
#define CCP_ERR_CMD_UNKNOWN       0x30u
#define CCP_ERR_PARAM             0x32u
#define CCP_ERR_ACCESS            0x33u
#define CCP_ERR_RESOURCE          0x35u

typedef struct CcpContext CcpContext;

typedef bool (*CcpTryTransmit)(void *user,
                               uint8_t channel,
                               const uint8_t frame[CCP_FRAME_SIZE]);

typedef struct {
    CcpTryTransmit tryTransmit;
    void *user;
} CcpTransport;

typedef struct {
    uint8_t addressExtension;
    uint32_t virtualBase;
    uint8_t *data;
    uint32_t size;
    uint8_t permissions;
} CcpMemoryRegion;

typedef bool (*CcpSelectCalibrationPage)(void *user, uint8_t page);
typedef bool (*CcpGetCalibrationPage)(void *user, uint8_t *page);

typedef struct {
    CcpSelectCalibrationPage selectPage;
    CcpGetCalibrationPage getPage;
    void *user;
} CcpCalibrationPageOps;

typedef enum {
    CCP_EXT_NOT_HANDLED = 0,
    CCP_EXT_HANDLED = 1
} CcpExtensionResult;

typedef CcpExtensionResult (*CcpExtensionCommand)(
    void *user,
    const uint8_t cro[CCP_FRAME_SIZE],
    uint8_t *returnCode,
    uint8_t responseData[CCP_CRM_PAYLOAD_MAX],
    uint8_t *responseLength);

typedef struct {
    CcpExtensionCommand seedKeyCommand;
    CcpExtensionCommand flashCommand;
    CcpExtensionCommand checksumCommand;
    void *user;
} CcpExtensionOps;

typedef struct {
    uint16_t stationAddress;
    uint8_t identityAddressExtension;
    uint32_t identityVirtualAddress;
    uint8_t identityLength;
} CcpConfig;

typedef struct {
    uint8_t addressExtension;
    uint32_t address;
} CcpMta;

typedef struct {
    uint8_t addressExtension;
    uint32_t address;
    uint8_t size;
    bool valid;
} CcpDaqEntry;

typedef struct {
    CcpDaqEntry entry[CCP_MAX_ENTRIES_PER_ODT];
    uint8_t totalBytes;
} CcpOdt;

typedef struct {
    CcpOdt odt[CCP_MAX_ODTS_PER_LIST];
    uint8_t lastOdt;
    uint8_t eventChannel;
    uint16_t prescaler;
    uint16_t prescalerCounter;
    bool selected;
    bool running;
} CcpDaqList;

typedef struct {
    uint8_t channel;
    uint8_t data[CCP_FRAME_SIZE];
} CcpTxItem;

typedef struct {
    CcpTxItem item[CCP_TX_QUEUE_DEPTH];
    uint8_t head;
    uint8_t tail;
    uint8_t count;
} CcpTxQueue;

struct CcpContext {
    CcpConfig config;
    CcpTransport transport;
    CcpCalibrationPageOps calibrationPage;
    CcpExtensionOps extension;
    const CcpMemoryRegion *regions;
    uint8_t regionCount;

    bool connected;
    uint8_t boundChannel;
    uint32_t inactivityMs;

    CcpMta mta[2];
    CcpDaqList daq[CCP_MAX_DAQ_LISTS];
    uint8_t daqPtrList;
    uint8_t daqPtrOdt;
    uint8_t daqPtrEntry;
    bool daqPtrValid;

    CcpTxQueue txQueue;
    bool txBusy;
    uint8_t txChannel;
    uint8_t txInFlight[CCP_FRAME_SIZE];

    uint32_t txOverrun;
    uint32_t txRejected;
    uint32_t txFailed;
    uint32_t memoryFaults;
};

static uint16_t Ccp_ReadLe16(const uint8_t *p)
{
    return (uint16_t)((uint16_t)p[0] |
                      ((uint16_t)p[1] << 8));
}

static uint32_t Ccp_ReadLe32(const uint8_t *p)
{
    return ((uint32_t)p[0]) |
           ((uint32_t)p[1] << 8) |
           ((uint32_t)p[2] << 16) |
           ((uint32_t)p[3] << 24);
}

static void Ccp_WriteLe32(uint8_t *p, uint32_t value)
{
    p[0] = (uint8_t)(value);
    p[1] = (uint8_t)(value >> 8);
    p[2] = (uint8_t)(value >> 16);
    p[3] = (uint8_t)(value >> 24);
}

static uint8_t *Ccp_Map(CcpContext *ctx,
                        uint8_t addressExtension,
                        uint32_t address,
                        uint8_t length,
                        bool writeAccess)
{
    uint32_t end;
    uint8_t i;

    if ((length == 0u) ||
        (address > (UINT32_MAX - (uint32_t)length))) {
        return NULL;
    }
    end = address + (uint32_t)length;

    for (i = 0u; i < ctx->regionCount; ++i) {
        const CcpMemoryRegion *region = &ctx->regions[i];
        uint32_t limit;
        uint8_t needed = writeAccess ? CCP_MEM_WRITE : CCP_MEM_READ;

        if ((region->data == NULL) ||
            (region->size == 0u) ||
            (region->addressExtension != addressExtension) ||
            ((region->permissions & needed) == 0u) ||
            (region->virtualBase > (UINT32_MAX - region->size))) {
            continue;
        }

        limit = region->virtualBase + region->size;
        if ((address >= region->virtualBase) && (end <= limit)) {
            return &region->data[address - region->virtualBase];
        }
    }
    return NULL;
}

static void Ccp_StopAllDaq(CcpContext *ctx)
{
    uint8_t i;

    for (i = 0u; i < CCP_MAX_DAQ_LISTS; ++i) {
        ctx->daq[i].running = false;
        ctx->daq[i].selected = false;
        ctx->daq[i].prescalerCounter = 0u;
    }
}

static void Ccp_ClearQueue(CcpContext *ctx)
{
    ctx->txQueue.head = 0u;
    ctx->txQueue.tail = 0u;
    ctx->txQueue.count = 0u;
}

static bool Ccp_QueuePush(CcpContext *ctx,
                          uint8_t channel,
                          const uint8_t frame[CCP_FRAME_SIZE])
{
    CcpTxItem *slot;

    if (ctx->txQueue.count >= CCP_TX_QUEUE_DEPTH) {
        ++ctx->txOverrun;
        return false;
    }

    slot = &ctx->txQueue.item[ctx->txQueue.tail];
    slot->channel = channel;
    memcpy(slot->data, frame, CCP_FRAME_SIZE);

    ctx->txQueue.tail =
        (uint8_t)((ctx->txQueue.tail + 1u) % CCP_TX_QUEUE_DEPTH);
    ++ctx->txQueue.count;
    return true;
}

static void Ccp_SendCrm(CcpContext *ctx,
                        uint8_t channel,
                        uint8_t ctr,
                        uint8_t returnCode,
                        const uint8_t *data,
                        uint8_t dataLength)
{
    uint8_t frame[CCP_FRAME_SIZE];

    memset(frame, 0, sizeof(frame));
    frame[0] = CCP_PID_CRM;
    frame[1] = returnCode;
    frame[2] = ctr;

    if ((data != NULL) && (dataLength <= CCP_CRM_PAYLOAD_MAX)) {
        memcpy(&frame[3], data, dataLength);
    }
    (void)Ccp_QueuePush(ctx, channel, frame);
}

static void Ccp_ServiceTx(CcpContext *ctx)
{
    CcpTxItem *front;
    bool accepted;

    if (ctx->txBusy ||
        (ctx->txQueue.count == 0u) ||
        (ctx->transport.tryTransmit == NULL)) {
        return;
    }

    front = &ctx->txQueue.item[ctx->txQueue.head];
    memcpy(ctx->txInFlight, front->data, CCP_FRAME_SIZE);
    ctx->txChannel = front->channel;

    /*
     * Contract: tryTransmit only accepts the request here. Completion is
     * reported later through Ccp_TxConfirmation().
     */
    ctx->txBusy = true;
    accepted = ctx->transport.tryTransmit(ctx->transport.user,
                                          ctx->txChannel,
                                          ctx->txInFlight);
    if (accepted) {
        ctx->txQueue.head =
            (uint8_t)((ctx->txQueue.head + 1u) % CCP_TX_QUEUE_DEPTH);
        --ctx->txQueue.count;
    } else {
        ctx->txBusy = false;
        ++ctx->txRejected;
    }
}

static bool Ccp_ReadMta(CcpContext *ctx,
                        uint8_t mtaIndex,
                        uint8_t *destination,
                        uint8_t length)
{
    uint8_t *source;

    if (mtaIndex >= 2u) {
        return false;
    }

    source = Ccp_Map(ctx,
                     ctx->mta[mtaIndex].addressExtension,
                     ctx->mta[mtaIndex].address,
                     length,
                     false);
    if (source == NULL) {
        ++ctx->memoryFaults;
        return false;
    }

    memcpy(destination, source, length);
    ctx->mta[mtaIndex].address += (uint32_t)length;
    return true;
}

static bool Ccp_WriteMta(CcpContext *ctx,
                         uint8_t mtaIndex,
                         const uint8_t *source,
                         uint8_t length)
{
    uint8_t *destination;

    if (mtaIndex >= 2u) {
        return false;
    }

    destination = Ccp_Map(ctx,
                          ctx->mta[mtaIndex].addressExtension,
                          ctx->mta[mtaIndex].address,
                          length,
                          true);
    if (destination == NULL) {
        ++ctx->memoryFaults;
        return false;
    }

    memcpy(destination, source, length);
    ctx->mta[mtaIndex].address += (uint32_t)length;
    return true;
}

static bool Ccp_DaqListIsValid(const CcpDaqList *list)
{
    uint8_t odtIndex;

    if ((list->lastOdt >= CCP_MAX_ODTS_PER_LIST) ||
        (list->eventChannel >= CCP_EVENT_CHANNEL_COUNT) ||
        (list->prescaler == 0u)) {
        return false;
    }

    for (odtIndex = 0u; odtIndex <= list->lastOdt; ++odtIndex) {
        const CcpOdt *odt = &list->odt[odtIndex];
        uint8_t entryIndex;
        uint8_t sum = 0u;

        if ((odt->totalBytes == 0u) ||
            (odt->totalBytes > CCP_DTO_DATA_MAX)) {
            return false;
        }

        for (entryIndex = 0u;
             entryIndex < CCP_MAX_ENTRIES_PER_ODT;
             ++entryIndex) {
            if (odt->entry[entryIndex].valid) {
                sum = (uint8_t)(sum + odt->entry[entryIndex].size);
            }
        }
        if (sum != odt->totalBytes) {
            return false;
        }
    }
    return true;
}

static bool Ccp_BuildDto(CcpContext *ctx,
                         uint8_t listIndex,
                         uint8_t odtIndex,
                         uint8_t frame[CCP_FRAME_SIZE])
{
    const CcpOdt *odt = &ctx->daq[listIndex].odt[odtIndex];
    uint8_t offset = 1u;
    uint8_t entryIndex;

    memset(frame, 0, CCP_FRAME_SIZE);
    frame[0] = (uint8_t)(listIndex * CCP_MAX_ODTS_PER_LIST +
                         odtIndex);

    for (entryIndex = 0u;
         entryIndex < CCP_MAX_ENTRIES_PER_ODT;
         ++entryIndex) {
        const CcpDaqEntry *entry = &odt->entry[entryIndex];
        uint8_t *source;

        if (!entry->valid) {
            continue;
        }
        if ((entry->size > (CCP_FRAME_SIZE - offset)) ||
            (entry->size == 0u)) {
            return false;
        }

        source = Ccp_Map(ctx,
                         entry->addressExtension,
                         entry->address,
                         entry->size,
                         false);
        if (source == NULL) {
            ++ctx->memoryFaults;
            return false;
        }

        memcpy(&frame[offset], source, entry->size);
        offset = (uint8_t)(offset + entry->size);
    }
    return true;
}

static void Ccp_AdvanceDaqPointer(CcpContext *ctx)
{
    ++ctx->daqPtrEntry;
    if (ctx->daqPtrEntry >= CCP_MAX_ENTRIES_PER_ODT) {
        ctx->daqPtrEntry = 0u;
        ++ctx->daqPtrOdt;
        if (ctx->daqPtrOdt >= CCP_MAX_ODTS_PER_LIST) {
            ctx->daqPtrValid = false;
        }
    }
}

static void Ccp_HandleWriteDaq(CcpContext *ctx,
                               uint8_t channel,
                               const uint8_t cro[CCP_FRAME_SIZE])
{
    uint8_t size = cro[2];
    uint8_t extension = cro[3];
    uint32_t address = Ccp_ReadLe32(&cro[4]);
    CcpOdt *odt;
    CcpDaqEntry *entry;
    uint8_t oldSize;
    uint8_t newTotal;

    if (!ctx->daqPtrValid ||
        (ctx->daqPtrList >= CCP_MAX_DAQ_LISTS) ||
        (ctx->daqPtrOdt >= CCP_MAX_ODTS_PER_LIST) ||
        (ctx->daqPtrEntry >= CCP_MAX_ENTRIES_PER_ODT)) {
        Ccp_SendCrm(ctx, channel, cro[1], CCP_ERR_PARAM, NULL, 0u);
        return;
    }

    if ((size != 1u) && (size != 2u) && (size != 4u)) {
        Ccp_SendCrm(ctx, channel, cro[1], CCP_ERR_PARAM, NULL, 0u);
        return;
    }

    if (Ccp_Map(ctx, extension, address, size, false) == NULL) {
        Ccp_SendCrm(ctx, channel, cro[1], CCP_ERR_ACCESS, NULL, 0u);
        return;
    }

    odt = &ctx->daq[ctx->daqPtrList].odt[ctx->daqPtrOdt];
    entry = &odt->entry[ctx->daqPtrEntry];
    oldSize = entry->valid ? entry->size : 0u;
    newTotal = (uint8_t)(odt->totalBytes - oldSize + size);

    if (newTotal > CCP_DTO_DATA_MAX) {
        Ccp_SendCrm(ctx, channel, cro[1], CCP_ERR_PARAM, NULL, 0u);
        return;
    }

    entry->addressExtension = extension;
    entry->address = address;
    entry->size = size;
    entry->valid = true;
    odt->totalBytes = newTotal;

    Ccp_AdvanceDaqPointer(ctx);
    Ccp_SendCrm(ctx, channel, cro[1], CCP_ERR_ACK, NULL, 0u);
}

static void Ccp_HandleStartStop(CcpContext *ctx,
                                uint8_t channel,
                                const uint8_t cro[CCP_FRAME_SIZE])
{
    uint8_t mode = cro[2];
    uint8_t listIndex = cro[3];
    uint8_t lastOdt = cro[4];
    uint8_t eventChannel = cro[5];
    uint16_t prescaler = Ccp_ReadLe16(&cro[6]);
    CcpDaqList *list;

    if ((listIndex >= CCP_MAX_DAQ_LISTS) ||
        (lastOdt >= CCP_MAX_ODTS_PER_LIST) ||
        (eventChannel >= CCP_EVENT_CHANNEL_COUNT) ||
        (prescaler == 0u) ||
        ((mode != 0u) && (mode != 1u) && (mode != 2u))) {
        Ccp_SendCrm(ctx, channel, cro[1], CCP_ERR_PARAM, NULL, 0u);
        return;
    }

    list = &ctx->daq[listIndex];
    list->lastOdt = lastOdt;
    list->eventChannel = eventChannel;
    list->prescaler = prescaler;
    list->prescalerCounter = 0u;

    if (!Ccp_DaqListIsValid(list)) {
        Ccp_SendCrm(ctx, channel, cro[1], CCP_ERR_PARAM, NULL, 0u);
        return;
    }

    if (mode == 0u) {
        list->running = false;
        list->selected = false;
    } else if (mode == 1u) {
        list->selected = true;
        list->running = true;
    } else {
        list->selected = true;
        list->running = false;
    }

    Ccp_SendCrm(ctx, channel, cro[1], CCP_ERR_ACK, NULL, 0u);
}

static void Ccp_HandleStartStopAll(CcpContext *ctx,
                                   uint8_t channel,
                                   const uint8_t cro[CCP_FRAME_SIZE])
{
    uint8_t mode = cro[2];
    uint8_t i;

    if ((mode != 0u) && (mode != 1u)) {
        Ccp_SendCrm(ctx, channel, cro[1], CCP_ERR_PARAM, NULL, 0u);
        return;
    }

    for (i = 0u; i < CCP_MAX_DAQ_LISTS; ++i) {
        if (mode == 0u) {
            ctx->daq[i].running = false;
            ctx->daq[i].selected = false;
            ctx->daq[i].prescalerCounter = 0u;
        } else if (ctx->daq[i].selected &&
                   Ccp_DaqListIsValid(&ctx->daq[i])) {
            ctx->daq[i].running = true;
            ctx->daq[i].prescalerCounter = 0u;
        }
    }

    Ccp_SendCrm(ctx, channel, cro[1], CCP_ERR_ACK, NULL, 0u);
}

static bool Ccp_TryExtension(CcpContext *ctx,
                             CcpExtensionCommand command,
                             uint8_t channel,
                             const uint8_t cro[CCP_FRAME_SIZE])
{
    uint8_t data[CCP_CRM_PAYLOAD_MAX];
    uint8_t dataLength = 0u;
    uint8_t returnCode = CCP_ERR_CMD_UNKNOWN;
    CcpExtensionResult result;

    if (command == NULL) {
        return false;
    }

    memset(data, 0, sizeof(data));
    result = command(ctx->extension.user,
                     cro,
                     &returnCode,
                     data,
                     &dataLength);

    if (result == CCP_EXT_NOT_HANDLED) {
        return false;
    }
    if (dataLength > CCP_CRM_PAYLOAD_MAX) {
        dataLength = 0u;
        returnCode = CCP_ERR_PARAM;
    }

    Ccp_SendCrm(ctx, channel, cro[1], returnCode, data, dataLength);
    return true;
}

static void Ccp_Dispatch(CcpContext *ctx,
                         uint8_t channel,
                         const uint8_t cro[CCP_FRAME_SIZE])
{
    uint8_t data[CCP_CRM_PAYLOAD_MAX];
    uint8_t command = cro[0];

    memset(data, 0, sizeof(data));

    switch (command) {
    case CCP_CMD_CONNECT:
    case CCP_CMD_TEST:
        Ccp_SendCrm(ctx, channel, cro[1], CCP_ERR_ACK, NULL, 0u);
        break;

    case CCP_CMD_DISCONNECT:
        Ccp_StopAllDaq(ctx);
        Ccp_ClearQueue(ctx);
        ctx->connected = false;
        ctx->boundChannel = CCP_NO_CHANNEL;
        ctx->inactivityMs = 0u;
        Ccp_SendCrm(ctx, channel, cro[1], CCP_ERR_ACK, NULL, 0u);
        break;

    case CCP_CMD_EXCHANGE_ID:
        ctx->mta[0].addressExtension =
            ctx->config.identityAddressExtension;
        ctx->mta[0].address =
            ctx->config.identityVirtualAddress;
        data[0] = ctx->config.identityLength;
        data[1] = 0u;       /* byte-array identifier */
        data[2] = 0x03u;    /* example: CAL + DAQ available */
        data[3] = 0x00u;    /* protection is not implemented here */
        Ccp_SendCrm(ctx, channel, cro[1], CCP_ERR_ACK, data, 4u);
        break;

    case CCP_CMD_SET_MTA: {
        uint8_t mtaIndex = cro[2];

        if (mtaIndex >= 2u) {
            Ccp_SendCrm(ctx, channel, cro[1],
                        CCP_ERR_PARAM, NULL, 0u);
            break;
        }
        ctx->mta[mtaIndex].addressExtension = cro[3];
        ctx->mta[mtaIndex].address = Ccp_ReadLe32(&cro[4]);
        Ccp_SendCrm(ctx, channel, cro[1],
                    CCP_ERR_ACK, NULL, 0u);
        break;
    }

    case CCP_CMD_UPLOAD: {
        uint8_t length = cro[2];

        if ((length == 0u) ||
            (length > CCP_CRM_PAYLOAD_MAX) ||
            !Ccp_ReadMta(ctx, 0u, data, length)) {
            Ccp_SendCrm(ctx, channel, cro[1],
                        CCP_ERR_ACCESS, NULL, 0u);
            break;
        }
        Ccp_SendCrm(ctx, channel, cro[1],
                    CCP_ERR_ACK, data, length);
        break;
    }

    case CCP_CMD_SHORT_UPLOAD: {
        uint8_t length = cro[2];
        CcpMta saved = ctx->mta[0];

        if ((length == 0u) ||
            (length > CCP_CRM_PAYLOAD_MAX)) {
            Ccp_SendCrm(ctx, channel, cro[1],
                        CCP_ERR_PARAM, NULL, 0u);
            break;
        }

        ctx->mta[0].addressExtension = cro[3];
        ctx->mta[0].address = Ccp_ReadLe32(&cro[4]);
        if (!Ccp_ReadMta(ctx, 0u, data, length)) {
            ctx->mta[0] = saved;
            Ccp_SendCrm(ctx, channel, cro[1],
                        CCP_ERR_ACCESS, NULL, 0u);
            break;
        }
        ctx->mta[0] = saved;
        Ccp_SendCrm(ctx, channel, cro[1],
                    CCP_ERR_ACK, data, length);
        break;
    }

    case CCP_CMD_DNLOAD: {
        uint8_t length = cro[2];

        if ((length == 0u) ||
            (length > CCP_CRO_DNLOAD_MAX)) {
            Ccp_SendCrm(ctx, channel, cro[1],
                        CCP_ERR_PARAM, NULL, 0u);
            break;
        }
        if (!Ccp_WriteMta(ctx, 0u, &cro[3], length)) {
            Ccp_SendCrm(ctx, channel, cro[1],
                        CCP_ERR_ACCESS, NULL, 0u);
            break;
        }

        data[0] = ctx->mta[0].addressExtension;
        Ccp_WriteLe32(&data[1], ctx->mta[0].address);
        Ccp_SendCrm(ctx, channel, cro[1],
                    CCP_ERR_ACK, data, 5u);
        break;
    }

    case CCP_CMD_DNLOAD6:
        if (!Ccp_WriteMta(ctx, 0u, &cro[2],
                          CCP_CRO_DNLOAD6_SIZE)) {
            Ccp_SendCrm(ctx, channel, cro[1],
                        CCP_ERR_ACCESS, NULL, 0u);
            break;
        }
        data[0] = ctx->mta[0].addressExtension;
        Ccp_WriteLe32(&data[1], ctx->mta[0].address);
        Ccp_SendCrm(ctx, channel, cro[1],
                    CCP_ERR_ACK, data, 5u);
        break;

    case CCP_CMD_GET_DAQ_SIZE: {
        uint8_t listIndex = cro[2];

        if (listIndex >= CCP_MAX_DAQ_LISTS) {
            Ccp_SendCrm(ctx, channel, cro[1],
                        CCP_ERR_PARAM, NULL, 0u);
            break;
        }
        data[0] = CCP_MAX_ODTS_PER_LIST;
        data[1] = (uint8_t)(listIndex *
                            CCP_MAX_ODTS_PER_LIST);
        Ccp_SendCrm(ctx, channel, cro[1],
                    CCP_ERR_ACK, data, 2u);
        break;
    }

    case CCP_CMD_SET_DAQ_PTR:
        if ((cro[2] >= CCP_MAX_DAQ_LISTS) ||
            (cro[3] >= CCP_MAX_ODTS_PER_LIST) ||
            (cro[4] >= CCP_MAX_ENTRIES_PER_ODT)) {
            ctx->daqPtrValid = false;
            Ccp_SendCrm(ctx, channel, cro[1],
                        CCP_ERR_PARAM, NULL, 0u);
            break;
        }
        ctx->daqPtrList = cro[2];
        ctx->daqPtrOdt = cro[3];
        ctx->daqPtrEntry = cro[4];
        ctx->daqPtrValid = true;
        Ccp_SendCrm(ctx, channel, cro[1],
                    CCP_ERR_ACK, NULL, 0u);
        break;

    case CCP_CMD_WRITE_DAQ:
        Ccp_HandleWriteDaq(ctx, channel, cro);
        break;

    case CCP_CMD_START_STOP:
        Ccp_HandleStartStop(ctx, channel, cro);
        break;

    case CCP_CMD_START_STOP_ALL:
        Ccp_HandleStartStopAll(ctx, channel, cro);
        break;

    case CCP_CMD_GET_SEED:
    case CCP_CMD_UNLOCK:
        if (!Ccp_TryExtension(ctx,
                              ctx->extension.seedKeyCommand,
                              channel,
                              cro)) {
            Ccp_SendCrm(ctx, channel, cro[1],
                        CCP_ERR_RESOURCE, NULL, 0u);
        }
        break;

    case CCP_CMD_CLEAR_MEMORY:
    case CCP_CMD_PROGRAM:
    case CCP_CMD_PROGRAM6:
        if (!Ccp_TryExtension(ctx,
                              ctx->extension.flashCommand,
                              channel,
                              cro)) {
            Ccp_SendCrm(ctx, channel, cro[1],
                        CCP_ERR_RESOURCE, NULL, 0u);
        }
        break;

    case CCP_CMD_BUILD_CHKSUM:
        if (!Ccp_TryExtension(ctx,
                              ctx->extension.checksumCommand,
                              channel,
                              cro)) {
            Ccp_SendCrm(ctx, channel, cro[1],
                        CCP_ERR_RESOURCE, NULL, 0u);
        }
        break;

    default:
        Ccp_SendCrm(ctx, channel, cro[1],
                    CCP_ERR_CMD_UNKNOWN, NULL, 0u);
        break;
    }
}

bool Ccp_Init(CcpContext *ctx,
              const CcpConfig *config,
              const CcpMemoryRegion *regions,
              uint8_t regionCount,
              const CcpTransport *transport,
              const CcpCalibrationPageOps *calibrationPage,
              const CcpExtensionOps *extension)
{
    uint8_t i;

    if ((ctx == NULL) ||
        (config == NULL) ||
        (regions == NULL) ||
        (regionCount == 0u) ||
        (transport == NULL) ||
        (transport->tryTransmit == NULL)) {
        return false;
    }

    for (i = 0u; i < regionCount; ++i) {
        if ((regions[i].data == NULL) ||
            (regions[i].size == 0u) ||
            (regions[i].virtualBase >
             (UINT32_MAX - regions[i].size))) {
            return false;
        }
    }

    memset(ctx, 0, sizeof(*ctx));
    ctx->config = *config;
    ctx->regions = regions;
    ctx->regionCount = regionCount;
    ctx->transport = *transport;
    ctx->boundChannel = CCP_NO_CHANNEL;
    ctx->txChannel = CCP_NO_CHANNEL;

    if (calibrationPage != NULL) {
        ctx->calibrationPage = *calibrationPage;
    }
    if (extension != NULL) {
        ctx->extension = *extension;
    }
    return true;
}

void Ccp_RxIndication(CcpContext *ctx,
                      uint8_t channel,
                      const uint8_t *cro,
                      uint8_t length)
{
    uint8_t command;
    uint16_t station;

    if ((ctx == NULL) ||
        (cro == NULL) ||
        (length != CCP_FRAME_SIZE)) {
        return;
    }

    command = cro[0];

    if ((command == CCP_CMD_CONNECT) ||
        (command == CCP_CMD_TEST)) {
        station = Ccp_ReadLe16(&cro[2]);
        if (station != ctx->config.stationAddress) {
            return;
        }

        if (command == CCP_CMD_CONNECT) {
            if (ctx->connected &&
                (ctx->boundChannel != channel)) {
                return;
            }
            ctx->connected = true;
            ctx->boundChannel = channel;
            ctx->inactivityMs = 0u;
        }

        Ccp_Dispatch(ctx, channel, cro);
        return;
    }

    if (!ctx->connected ||
        (ctx->boundChannel != channel)) {
        Ccp_SendCrm(ctx, channel, cro[1],
                    CCP_ERR_ACCESS, NULL, 0u);
        return;
    }

    ctx->inactivityMs = 0u;
    Ccp_Dispatch(ctx, channel, cro);
}

void Ccp_MainFunction(CcpContext *ctx)
{
    if (ctx == NULL) {
        return;
    }

    if (ctx->connected) {
        if (ctx->inactivityMs < UINT32_MAX) {
            ++ctx->inactivityMs;
        }

        if (ctx->inactivityMs >= CCP_SESSION_TIMEOUT_MS) {
            Ccp_StopAllDaq(ctx);
            Ccp_ClearQueue(ctx);
            ctx->connected = false;
            ctx->boundChannel = CCP_NO_CHANNEL;
            ctx->inactivityMs = 0u;
            /*
             * Do not reuse txInFlight until a late hardware confirmation
             * arrives. Pending queued frames are gone, and no new DAQ is made.
             */
        }
    }

    Ccp_ServiceTx(ctx);
}

void Ccp_TxConfirmation(CcpContext *ctx,
                        uint8_t channel,
                        bool success)
{
    if ((ctx == NULL) ||
        !ctx->txBusy ||
        (ctx->txChannel != channel)) {
        return;
    }

    if (!success) {
        ++ctx->txFailed;
    }
    ctx->txBusy = false;
    ctx->txChannel = CCP_NO_CHANNEL;
}

void Ccp_Event(CcpContext *ctx, uint8_t eventChannel)
{
    uint8_t listIndex;

    if ((ctx == NULL) ||
        !ctx->connected ||
        (eventChannel >= CCP_EVENT_CHANNEL_COUNT)) {
        return;
    }

    for (listIndex = 0u;
         listIndex < CCP_MAX_DAQ_LISTS;
         ++listIndex) {
        CcpDaqList *list = &ctx->daq[listIndex];
        uint8_t odtIndex;

        if (!list->running ||
            (list->eventChannel != eventChannel)) {
            continue;
        }

        ++list->prescalerCounter;
        if (list->prescalerCounter < list->prescaler) {
            continue;
        }
        list->prescalerCounter = 0u;

        for (odtIndex = 0u;
             odtIndex <= list->lastOdt;
             ++odtIndex) {
            uint8_t frame[CCP_FRAME_SIZE];

            if (!Ccp_BuildDto(ctx,
                              listIndex,
                              odtIndex,
                              frame)) {
                list->running = false;
                break;
            }

            if (!Ccp_QueuePush(ctx,
                               ctx->boundChannel,
                               frame)) {
                return;
            }
        }
    }
}

/* ---------------- Minimal CAN adapter and executable demo ---------------- */

#define EXAMPLE_CCP_CRO_CAN_ID  0x650u
#define EXAMPLE_CCP_DTO_CAN_ID  0x651u
#define EXAMPLE_CAN_CHANNEL     0u

typedef struct {
    CcpContext *ccp;
    bool hardwareBusy;
    uint8_t pendingChannel;
    uint8_t pendingFrame[CCP_FRAME_SIZE];
} DemoCanAdapter;

typedef struct {
    uint8_t activeCalibrationPage;
} DemoApplication;

static bool DemoCan_TryTransmit(void *user,
                                uint8_t channel,
                                const uint8_t frame[CCP_FRAME_SIZE])
{
    DemoCanAdapter *adapter = (DemoCanAdapter *)user;
    uint8_t i;

    if (adapter->hardwareBusy) {
        return false;
    }

    /*
     * A real driver queues EXAMPLE_CCP_DTO_CAN_ID here. This demo copies the
     * bytes to stable driver-owned storage and completes it later.
     */
    memcpy(adapter->pendingFrame, frame, CCP_FRAME_SIZE);
    adapter->pendingChannel = channel;
    adapter->hardwareBusy = true;

    printf("TX ch%u id=0x%03X :", (unsigned)channel,
           (unsigned)EXAMPLE_CCP_DTO_CAN_ID);
    for (i = 0u; i < CCP_FRAME_SIZE; ++i) {
        printf(" %02X", (unsigned)frame[i]);
    }
    printf("\n");
    return true;
}

static void DemoCan_Rx(DemoCanAdapter *adapter,
                       uint8_t channel,
                       uint16_t canId,
                       const uint8_t data[CCP_FRAME_SIZE])
{
    if ((canId == EXAMPLE_CCP_CRO_CAN_ID) &&
        (channel == EXAMPLE_CAN_CHANNEL)) {
        Ccp_RxIndication(adapter->ccp,
                         channel,
                         data,
                         CCP_FRAME_SIZE);
    }
}

static void DemoCan_TxInterrupt(DemoCanAdapter *adapter,
                                bool success)
{
    uint8_t channel;

    if (!adapter->hardwareBusy) {
        return;
    }

    channel = adapter->pendingChannel;
    adapter->hardwareBusy = false;
    Ccp_TxConfirmation(adapter->ccp, channel, success);
}

static void Demo_DrainTx(DemoCanAdapter *adapter)
{
    while (adapter->hardwareBusy ||
           (adapter->ccp->txQueue.count != 0u)) {
        if (adapter->hardwareBusy) {
            DemoCan_TxInterrupt(adapter, true);
        }
        Ccp_MainFunction(adapter->ccp);
    }
}

static void Demo_SendCro(DemoCanAdapter *adapter,
                         const uint8_t cro[CCP_FRAME_SIZE])
{
    DemoCan_Rx(adapter,
               EXAMPLE_CAN_CHANNEL,
               EXAMPLE_CCP_CRO_CAN_ID,
               cro);
    Ccp_MainFunction(adapter->ccp);
    Demo_DrainTx(adapter);
}

static bool Demo_SelectCalibrationPage(void *user, uint8_t page)
{
    DemoApplication *app = (DemoApplication *)user;

    if (page > 1u) {
        return false;
    }

    /*
     * A target-specific adapter may switch OVC/MPU mappings here, verify the
     * hardware state, then update activeCalibrationPage only on success.
     */
    app->activeCalibrationPage = page;
    return true;
}

static bool Demo_GetCalibrationPage(void *user, uint8_t *page)
{
    DemoApplication *app = (DemoApplication *)user;

    if (page == NULL) {
        return false;
    }
    *page = app->activeCalibrationPage;
    return true;
}

int main(void)
{
    static uint8_t identity[8] = {
        'C', 'C', 'P', '2', '1', '-', 'D', 'E'
    };
    static uint8_t measurements[32];
    static uint8_t calibration[32];
    static CcpContext ccp;
    static DemoCanAdapter canAdapter;
    static DemoApplication app;

    uint32_t speedRaw = 123456u;
    uint16_t currentRaw = 320u;
    uint8_t stateRaw = 5u;

    const CcpMemoryRegion regions[] = {
        {0u, 0x10000000u, identity, sizeof(identity),
         CCP_MEM_READ},
        {0u, 0x20000000u, measurements, sizeof(measurements),
         CCP_MEM_READ},
        {0u, 0x30000000u, calibration, sizeof(calibration),
         CCP_MEM_READ | CCP_MEM_WRITE}
    };
    const CcpConfig config = {
        0x0001u, 0u, 0x10000000u, sizeof(identity)
    };
    CcpTransport transport;
    CcpCalibrationPageOps pageOps;

    const uint8_t connect[8] =
        {0x01u, 0x01u, 0x01u, 0x00u, 0u, 0u, 0u, 0u};
    const uint8_t setMtaCalibration[8] =
        {0x02u, 0x02u, 0x00u, 0x00u,
         0x00u, 0x00u, 0x00u, 0x30u};
    const uint8_t downloadValue[8] =
        {0x03u, 0x03u, 0x04u, 0x78u,
         0x56u, 0x34u, 0x12u, 0x00u};
    const uint8_t uploadValue[8] =
        {0x04u, 0x04u, 0x04u, 0u, 0u, 0u, 0u, 0u};

    const uint8_t getDaqSize[8] =
        {0x14u, 0x10u, 0x00u, 0u, 0u, 0u, 0u, 0u};
    const uint8_t setDaqPtr[8] =
        {0x15u, 0x11u, 0x00u, 0x00u,
         0x00u, 0u, 0u, 0u};
    const uint8_t writeSpeed[8] =
        {0x16u, 0x12u, 0x04u, 0x00u,
         0x00u, 0x00u, 0x00u, 0x20u};
    const uint8_t writeCurrent[8] =
        {0x16u, 0x13u, 0x02u, 0x00u,
         0x04u, 0x00u, 0x00u, 0x20u};
    const uint8_t writeState[8] =
        {0x16u, 0x14u, 0x01u, 0x00u,
         0x06u, 0x00u, 0x00u, 0x20u};
    const uint8_t startDaq[8] =
        {0x06u, 0x15u, 0x01u, 0x00u,
         0x00u, CCP_EVENT_10MS, 0x01u, 0x00u};
    const uint8_t stopAll[8] =
        {0x08u, 0x16u, 0x00u, 0u, 0u, 0u, 0u, 0u};

    memcpy(&measurements[0], &speedRaw, sizeof(speedRaw));
    memcpy(&measurements[4], &currentRaw, sizeof(currentRaw));
    memcpy(&measurements[6], &stateRaw, sizeof(stateRaw));

    canAdapter.ccp = &ccp;
    transport.tryTransmit = DemoCan_TryTransmit;
    transport.user = &canAdapter;

    pageOps.selectPage = Demo_SelectCalibrationPage;
    pageOps.getPage = Demo_GetCalibrationPage;
    pageOps.user = &app;

    if (!Ccp_Init(&ccp,
                  &config,
                  regions,
                  (uint8_t)(sizeof(regions) / sizeof(regions[0])),
                  &transport,
                  &pageOps,
                  NULL)) {
        return 1;
    }

    /* CONNECT -> SET_MTA -> DNLOAD -> UPLOAD */
    Demo_SendCro(&canAdapter, connect);
    Demo_SendCro(&canAdapter, setMtaCalibration);
    Demo_SendCro(&canAdapter, downloadValue);

    /* DNLOAD advanced MTA; set it again before reading back. */
    Demo_SendCro(&canAdapter, setMtaCalibration);
    Demo_SendCro(&canAdapter, uploadValue);

    /*
     * GET_DAQ_SIZE -> SET_DAQ_PTR -> WRITE_DAQ x3 -> START_STOP.
     * One DTO becomes PID + 4-byte speed + 2-byte current + 1-byte state.
     */
    Demo_SendCro(&canAdapter, getDaqSize);
    Demo_SendCro(&canAdapter, setDaqPtr);
    Demo_SendCro(&canAdapter, writeSpeed);
    Demo_SendCro(&canAdapter, writeCurrent);
    Demo_SendCro(&canAdapter, writeState);
    Demo_SendCro(&canAdapter, startDaq);

    /* Called by the real 10 ms task. Prescaler=1, so one DTO is queued. */
    Ccp_Event(&ccp, CCP_EVENT_10MS);
    Ccp_MainFunction(&ccp);
    Demo_DrainTx(&canAdapter);

    Demo_SendCro(&canAdapter, stopAll);
    return 0;
}

四、代码里的安全边界逐项解释

1. 8 字节帧先整体清零

Ccp_SendCrm()Ccp_BuildDto() 都先对完整 8 字节数组执行 memset(..., 0, 8)。因此 CRM/DTO 未使用字节保持为 0,不会把旧栈内容或上一帧残留带到总线上。局部构帧数组只作为同步输入复制进队列,异步驱动实际收到的是 Context 内持续有效的 txInFlight

2. 地址不是指针,而是白名单中的协议虚拟地址

Ccp_Map() 同时检查地址扩展、读写权限、address + length 的整数溢出以及整个半开区间 [address, address + length)。只有完整区间落在同一个 MemoryRegion 中才返回实际缓冲区指针;仅首地址合法但尾端越界、跨 Region 或越过 UINT32_MAX 都会失败。

3. CRO/CRM 的有效负载上限分开检查

CRM 的 3..7 字节最多承载 5 字节,所以 UPLOADSHORT_UPLOAD 拒绝 0 或大于 5 的长度;普通 DNLOAD 的 CRO 数据区同样最多 5 字节。DNLOAD6 是独立命令,固定从 CRO 的 2..7 字节写入 6 字节,不能拿普通 DNLOAD 的长度字段套用。

4. DAQ 三层下标和一帧总长度都受控

SET_DAQ_PTR 对 List、ODT、Entry 全部使用 >= 上限判断。WRITE_DAQ 仅接受 1/2/4 字节,先验证完整只读地址区间,再用“旧 Entry 长度扣除 + 新长度加入”计算 ODT 总长度,保证不超过 7。START_STOP 检查 lastOdteventChannelprescaler 和全部待发送 ODT 的一致性。

5. 环形队列明确区分空与满

队列保留 headtailcountcount == 0 是空,count == depth 是满;满时增加 txOverrun 并拒绝新帧,绝不覆盖尚未发送的数据。发送请求被驱动接受后才从队列弹出;硬件完成后由 Ccp_TxConfirmation() 清除 txBusy

6. 超时清现场,但不破坏在途缓冲区

1 ms 主函数累计 10 秒无有效活动后停止所有 DAQ、清空待发送队列并解除 CAN 通道绑定。若硬件已有一帧在途,样例保留 txInFlighttxBusy,直到迟到的 TxConfirmation 返回,避免异步驱动仍引用缓冲区时被复用。量产驱动若支持可靠取消,可在传输适配层增加取消与确认状态机。

五、典型接入顺序

CAN Rx ISR/task          -> Ccp_RxIndication(&ccp, channel, data, 8)
1 ms task/main loop      -> Ccp_MainFunction(&ccp)
10 ms periodic task      -> Ccp_Event(&ccp, CCP_EVENT_10MS)
100 ms periodic task     -> Ccp_Event(&ccp, CCP_EVENT_100MS)
CAN Tx complete callback -> Ccp_TxConfirmation(&ccp, channel, success)

Ccp_MainFunction() 的时间基准必须稳定为 1 ms;若工程只能提供其他周期,应把“本次经过的毫秒数”作为参数,或在适配层做累计,不能直接沿用 10 秒常量。10 ms 与 100 ms 事件入口可以在不同任务调用,但共享 Context 时需要按项目并发模型加入短临界区。

六、两组完整交互示例

1. CONNECT → SET_MTA → DNLOAD → UPLOAD

CONNECT
  -> SET_MTA(MTA0, ext=0, address=0x30000000)   [example address]
  -> DNLOAD(4, 78 56 34 12)
  -> SET_MTA(MTA0, ext=0, address=0x30000000)
  -> UPLOAD(4)
  <- CRM payload: 78 56 34 12

成功 DNLOAD 会推进 MTA,所以读回前重新 SET_MTA。示例虚拟地址 0x30000000 只映射到本地 calibration[] 缓冲区;它不代表任何真实 ECU 内存布局。

2. 三个变量以 4 + 2 + 1 字节组成一帧 DTO

GET_DAQ_SIZE(list=0)
  -> SET_DAQ_PTR(list=0, odt=0, entry=0)
  -> WRITE_DAQ(size=4, address=0x20000000)      [example address]
  -> WRITE_DAQ(size=2, address=0x20000004)      [example address]
  -> WRITE_DAQ(size=1, address=0x20000006)      [example address]
  -> START_STOP(mode=1, list=0, lastOdt=0,
                event=10ms, prescaler=1)

10 ms Event:
DTO = PID | speed[4] | current[2] | state[1]
DTO 字节内容长度
0PID(样例 List 0 / ODT 0 为 0)1
1..4speedRaw,示例虚拟地址 0x200000004
5..6currentRaw,示例虚拟地址 0x200000042
7stateRaw,示例虚拟地址 0x200000061

三个 WRITE_DAQ 完成后,ODT 数据总长刚好是 7;再加入任何 Entry 都会被拒绝。若 Prescaler 设为 5,并绑定 10 ms Event,则每第 5 次事件采集一次,发送周期为 50 ms。要批量启动,可先用 START_STOP(mode=2) 选择多个 List,再用 START_STOP_ALL(mode=1) 启动。

七、标准 CCP 行为与样例简化点

主题CCP 2.1 通用行为本文样例的选择
CRO/CRM8 字节命令与响应,CTR 关联请求保留 PID/返回码/CTR;只实现列出的子集
字节序主从需按约定解释多字节字段固定 Little-endian,接入时必须与工具一致
地址MTA 由地址扩展和地址组成地址被解释为白名单虚拟地址,不允许裸指针
DAQList → ODT → Entry,由事件和 Prescaler 触发静态 2 List × 4 ODT × 7 Entry;PID 采用简单连续分配
会话连接、命令活动、断开单活动会话,10 秒超时,首次 CONNECT 绑定通道
EXCHANGE_ID报告身份长度/资源并通过 MTA 读取标识标识位于只读白名单 Region,资源位仅示例
标定页协议表达页面选择与查询核心只定义回调契约;OVC/硬件动作由应用适配并回读确认
安全/编程可扩展资源解锁、校验和与编程流程默认返回资源不可用;只有接口,没有伪实现

为了让样例保持连续、可读,CRM 与 DAQ DTO 共用一个 FIFO,且没有实现命令优先级、DAQ 动态内存分配、完整 Session Status、事件报文和所有 CCP 可选命令。量产系统通常要给 CRM 更高优先级,并把容量和事件描述从 ECU/A2L 的共同配置生成。

八、标定页、Seed&Key 与编程扩展如何接入

CcpCalibrationPageOps 只定义 selectPage/getPage。目标适配应在 select 回调中执行“请求切换 → 配置 OVC/MPU/软件映射 → 回读硬件状态 → 成功后更新活动页”;协议核心不应该包含具体寄存器地址。本文没有把某款 MCU 的 OVC 写法包装成通用实现。

CcpExtensionOps 分别预留 Seed&Key、Flash Programming 和异步 Checksum 命令处理器。回调为空时明确返回资源不可用。真正接入时,Seed 和 Key 的生成/验证应由安全模块或 HSM 承担;Flash 擦写/编程必须有电压、会话、地址、完整性与回滚状态机;大块 Checksum 应异步分片,而不是在 CAN Rx 上下文阻塞计算。

九、从样例到量产还缺什么

  • Seed&Key / HSM:按 CAL、DAQ、PGM 资源分权,加入失败计数、延时、防重放和会话重锁。
  • 多核并发与临界区:保护 Rx、Event、MainFunction、TxConfirmation 共享的队列、DAQ 配置和会话状态,定义 ISR/任务可调用关系。
  • 异步 Checksum 状态机:分片读取、可取消、可查询进度,并限制最坏执行时间。
  • 标定页一致性与掉电保存:硬件映射回读、Working/Reference Page 状态一致、NVM 提交流程与掉电恢复。
  • A2L/ELF 版本一致性:构建产物绑定版本、地址自动校验、工具侧拒绝不匹配文件。
  • 总线负载与 DAQ 预算:按位填充、仲裁、周期、抖动和最坏峰值计算容量,设计 CRM 优先级和降载策略。
  • MISRA、静态分析、单元测试和故障注入:覆盖边界长度、地址溢出、跨区访问、队列满、迟到确认、超时重连、非法 DAQ 下标和并发重配置。

十、落地检查清单

  1. 先确定工具与 ECU 的 Station Address、CRO/DTO CAN ID、字节序和地址扩展规则;本文值都只是示例。
  2. 把 A2L 暴露的每段地址转换成最小权限的 MemoryRegion,禁止把整个 MCU 地址空间放进白名单。
  3. 接通 Rx、1 ms MainFunction、10/100 ms Event 和 TxConfirmation 四个入口。
  4. 先验证 CONNECT 与 MTA 读写,再配置单 ODT 的 4+2+1 DTO,最后才扩大 DAQ 数量和频率。
  5. 注入越界地址、长度 0/6、Entry 下标等于上限、ODT 超过 7 字节、队列满和超时断线,确认均可控失败。
  6. 启用量产安全、并发保护、总线预算、版本一致性和持久化方案后,再把它称为产品级协议栈。

一个可移植 CCP 从站的关键并不在命令 switch 写得多长,而在接口契约是否稳定:协议只接受受控虚拟地址,DAQ 只读取已验证 Entry,发送缓冲区活到硬件确认,会话结束能停止数据流并释放通道。守住这些边界,后续替换 CAN 驱动、内存布局或标定页硬件时,协议核心才不需要跟着重写。

文末附加内容

评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇