ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

【AUTOSAR】CanTp(CAN 传输层) 从入门到放弃

【AUTOSAR】CanTp(CAN 传输层) 从入门到放弃 【AUTOSAR】CanTpCAN 传输层 从入门到放弃1. CanTp 是干什么的1.1 为什么需要它Classic CAN 单帧载荷最多 8 字节CAN FD 也只有 64 字节。但诊断响应、刷写数据块动辄几百字节到几 KB——读一个 DID、下一个固件 Block都不可能塞进一张帧里。切片、重组、流控这套活总得有人干。放到应用层上层软件就和硬件长度绑死了所以 AUTOSAR 把它独立成CanTp模块按照 ISO 15765-2 实现向上提供长报文的透明分片与重组。1.2 N-SDU 与 N-PDU两个数据单元的名字要先分清N-SDU完整的长报文比如一条 1024 字节的诊断响应。上层PduR/DCM跟 CanTp 之间打交道的就是它。N-PDU切片后映射到一张 CAN 帧上的片段——PCI协议控制信息加一段数据。CanTp 跟 CanIf 之间打交道的是它。另外说明一句SWS 规范里只把 CanTp 定义为夹在 PduR 与 CanIf 之间各类资料有的把它画进服务层、有的画进 ECU 抽象层——那些都是画图人的归类不是规范原文。2. 四种协议帧ISO 15765-2 把 N-PDU 分成四种帧单帧 SF、首帧 FF、连续帧 CF、流控帧 FC。每种帧的身份和参数都写在 Payload 首字节的高 4 位里。2.1 PCI 字节布局几个容易记错的点SFClassic CANSF_DL 在字节 0 的低 4 位取值 1~7用了扩展/混合寻址时首字节被地址占了只剩 1~6。SFCAN FD数据超过 7 字节用逃逸格式——字节 0 固定0x00真正的长度挪到字节 1最大 62。FF12 位长度最大 4095 字节超过它CAN FD 大报文用 32 位扩展格式0x10 0x00加 4 字节长度数据从字节 6 开始。CF低 4 位是序号 SN第一帧 CF 固定为 1之后 1→2→…→15→0 轮转。CAN FD 下还有一个容易忽略的细节接收侧判一帧 CF 带多少数据时规范规定 CAN_DL ≤ 8 的帧按 8 字节算RX_DL8大于 8 才按实际长度算SWS_CanTp_00350——Classic CAN 时代的短帧补齐到 8这套约定延续到了 FD 上。2.2 流控帧 FC 的三个字段FC 是接收方控制发送方节奏的地方三个字段各有分工FS流状态0x0CTS 授权继续发0x1WAIT 接收端忙让对方暂停再等一次 FC次数受 WFTmax 限制0x2OVFLW 缓冲区装不下整个会话终止。BS块大小发送方连发 BS 帧 CF 后必须停下等下一个 FC。BS0 表示剩下的 CF 一口气发完。STmin最小帧间隔两个 CF 之间的最小间隔。0x00~0x7F按毫秒算0~127ms0xF1~0xF9按 100µs 算100900µs**保留值0x800xF0、0xFA~0xFF按最长的 127ms 处理**ISO 15765-2。两个常见的想当然STmin0x00 不等于零间隔——它只是没有最小间隔要求实际节奏仍受 N_As 超时、总线仲裁负载、上层供数速度限制。FS/BS/STmin 都是接收方写进 FC 的命令所以 CanTpSTmin、CanTpBs 挂在 RxNSdu接收侧配置。配到发送侧不会生效这是配置时最常见的一类错误。3. 寻址模式3.1 五种寻址格式地址信息有的完全藏在 CAN ID 的分配约定里有的要占掉 Payload 的第一个字节寻址格式CAN ID逻辑地址位置PCI 起始场景Standard正常11 / 29 位完全隐含在 ID 分配里字节 0点对点专用诊断通道Extended扩展11 / 29 位N_TA 占 Payload 首字节字节 1多节点共用同一 CAN IDMixed 11-bit11 位N_AE 占首字节字节 1子网网关、重定向Normal Fixed固定29 位N_SA/N_TA 打包进 ID字节 0J1939 / SAE 诊断网络Mixed 29-bit29 位ID 打包 SA/TA首字节放 N_AE字节 1J1939 混合多子网注意占首字节的模式里CanIf 报上来的 Payload 第一个字节是地址、不是帧类型——解析代码里别拿字节 0 当 PCI这是移植代码时很常见的一类笔误。3.2 泛型连接与 MetaData如果对端 ECU 很多网关动态诊断路由、Bus Mirroring 这类场景为每个对端静态配一对CanTpRxNSdu/CanTpTxNSdu既费配置也费内存。AUTOSAR 提供了泛型连接Generic Connection地址作为元数据挂在PduInfoType.MetaDataPtr里随数据传递。接收方向CanIf 按寻址模式从 CAN ID 或 Payload 首字节提取 N_SA/N_TA/N_AE封进MetaDataPtrCanTp 调PduR_CanTpStartOfReception/CopyRxData时原样向上透传DCM 按元数据分流。发送方向上层调CanTp_Transmit时把目标地址放进MetaDataPtrCanTp 保存下来组 SF/FF/CF/FC 下发CanIf_Transmit时回填。开关CanTpGenericConnectionSupport需与CanTpDynIdSupport一起打开。元数据类型是SOURCE_ADDRESS_16、TARGET_ADDRESS_16、ADDRESS_EXTENSION_8。4. 收发数据流CanTp 和上层之间是零拷贝的流式握手缓冲区永远是上层的CanTp 只负责申请、逐片搬运、最后还回。4.1 接收侧按这张图走一遍收到 FFCanIf 回调CanTp_RxIndication。CanTp 解析出 FF_DL总长。CanTp 调PduR_CanTpStartOfReception(RxPduId, PduInfoPtr, TpSduLength, bufferSizePtr)向上层要缓冲。返回值决定后续BUFREQ_OK分配成功bufferSizePtr返回可用空间。CanTp 回FC(CTS)。BUFREQ_E_OVFL装不下这么长的报文。CanTp 回FC(OVFLW)本次接收终止。BUFREQ_E_NOT_OK上层出错。CanTp一个 FC 都不发静默终止。每收一帧 CFSN 校验通过后调PduR_CanTpCopyRxData把数据拷进上层缓冲。收满 FF_DL 后调PduR_CanTpRxIndication(RxPduId, E_OK)。SN 不对或 N_Cr 超时 → 终止接收RxIndication(E_NOT_OK)。4.2 发送侧几个容易误解的点CanTp_Transmit(TxPduId, PduInfoPtr)是异步的E_OK 只代表受理不代表发完。此时PduInfoPtr里只用得上SduLength数据指针可以为空。数据是 CanTp 逐片从上层拉的每发一帧前调PduR_CanTpCopyTxData取数据。CopyTxData返回BUFREQ_E_BUSY说明上层一时给不出数据——CanTp 按retryInfo回退计数延后重试不算失败。发送节奏由收到的 FC 决定BS 帧歇一次帧间隔不小于 STmin。5. 超时监控CanTp 有六个定时器和一个次数上限全部对齐 ISO 15765-2 的网络层时序参数定时器方向配置参数开始复位超时动作N_As发送方CanTpNasTxNSdu请求 CanIf 发任意 N-PDUCanTp_TxConfirmation终止会话 → TxConfirmation(E_NOT_OK)N_Bs发送方CanTpNbsTxNSduFF或一块最后一帧 CF的发送确认收到合法 FC终止发送 → TxConfirmation(E_NOT_OK)N_Cs发送方CanTpNcsTxNSdu收到 FC(CTS) 或上一帧 CF 确认CopyTxData 备好下一帧终止发送 → TxConfirmation(E_NOT_OK)N_Ar接收方CanTpNarRxNSdu请求 CanIf 发送 FCFC 的 TxConfirmation终止接收 → RxIndication(E_NOT_OK)N_Br接收方CanTpNbrRxNSdu收到 FF 或一个 Block 的最后一帧 CF发出 FC见下方说明N_Cr接收方CanTpNcrRxNSduFC(CTS) 发出 / 收到上一帧 CF收到序号正确的下一帧 CF终止接收 → RxIndication(E_NOT_OK)两点说明N_Br 在 ISO 里是性能要求N_Ar N_Br 0.9 × N_BsN_Cs 同理——它们本来描述的是两步之间的时间预算AUTOSAR 把它们也做成了可配的定时器。这张表是六份规范资料里最容易张冠李戴的地方。WFTmaxCanTpWftMax配 0 不允许 WAIT接收端连续回 FC(WAIT) 的次数上限。到上限后终止接收并通知上层 E_NOT_OK之后一个 FC 都不再发——对面的发送端随后自己 N_Bs 超时收场。6. 取消传输与运行时改参数6.1 取消传输CanTp_CancelTransmit(TxPduId)正在发送时调用会立即终止会话并回调PduR_CanTpTxConfirmation通知取消结果——不同版本规范对结果值写法不一新一些的版本写NTFRSLT_E_CANCELATION_OK旧版写E_NOT_OK以项目用的 SWS 为准。不在发送时调用返回 E_NOT_OK 并报 DETCANTP_E_OPER_NOT_SUPPORTED。这个 API 由静态开关CanTpTc控制。CanTp_CancelReceive(RxPduId)两类拒绝——单帧接收中没什么可取消的或最后一帧 CF 已在处理N_Cr 已对最后一帧启动。取消成功回调PduR_CanTpRxIndication(E_NOT_OK)。取消之后如果对端还在等 CF它会 N_Cr 超时——这是预期行为不是 bug。6.2 运行时改参数CanTp_ChangeParameter(id, parameter, value)只能改两个参数TP_BS、TP_STMIN——都是接收侧参数写进 FC 发给对方的命令。关键约束接收进行中调用会立即返回 E_NOT_OK什么都不改SWS_CanTp_00303/00304。不是改了下个会话生效而是现在改不动被拒了要改就等这轮接收结束。7. 填充与功能寻址约束7.1 填充Padding发送侧CanTpTxPaddingActivation CANTP_ONSF、最后一帧 CF、FC 不足 8 字节时用CanTpPaddingByte常见 0x55/0xAA补齐CanTp 与 CanIf 之间永远按 8 字节走。接收侧CanTpRxPaddingActivation CANTP_ON只接受 8 字节的 SF 和最后一帧 CF。SF 短于 8 → 拒收该帧 DETCANTP_E_PADDINGSWS_CanTp_00345最后一帧 CF 短于 8 → 终止整个接收RxIndication(E_NOT_OK) DETSWS_CanTp_00346——两条的后果不一样后者砍掉的是一整轮会话。接收侧配 OFF 也有长度检查过短的帧直接忽略只是不报 DETSWS_CanTp_00098。两种模式下CanTp 都只把有效数据字节传给上层SWS_CanTp_00116——填充字节永远不会混进上层数据上层看到的长度由 PCI 里的 DL 决定。7.2 功能寻址发送侧功能寻址只许单帧。想发多帧 → E_NOT_OK DETCANTP_E_INVALID_TATYPE。接收侧功能寻址通道上收到 FF/CF/FC 一律忽略、不回任何流控ISO 15765-2 的规定。原因很直白广播 1:N 没有唯一的接收方能回 FC(CTS)多帧握手机制根本跑不起来。要收长报文用物理寻址。8. 配置要点配置分三层CanTpGeneral全局开关→CanTpConfig→CanTpChannel半双工/全双工→RxNSdu/TxNSdu。定时器和流控参数的归属要记牢参数容器说明CanTpNas / CanTpNbs / CanTpNcsTxNSdu发送侧三个定时器CanTpNar / CanTpNbr / CanTpNcrRxNSdu接收侧三个定时器CanTpSTmin / CanTpBsRxNSdu写进 FC 的命令挂在接收侧CanTpWftMaxRxNSdu连续 FC(WAIT) 次数上限CanTpPaddingByteGeneral填充值CanTpChannelModeChannelFULL_DUPLEX / HALF_DUPLEX通道是收发共享的资源三条规则SWS 原文要求一个 N-SDU 只挂一个通道通道忙时新的CanTp_Transmit直接被拒E_NOT_OK——想并发就得多配通道。同通道正在接收时又来一帧 FF放弃当前接收通知上层 E_NOT_OK把这帧 FF 当作新接收的开始——这是规范规定的抢占行为不是 bug。两个并发链路共用一个CanTpChannel时就会互相打断这才是要避免的。通道数量不是直接配的由配置工具根据 N-SDU/Channel 路由表推导。9. API 清单9.1 提供给上层的标准 APIAPI作用CanTp_Init(CfgPtr)初始化模块与所有通道状态机CanTp_Shutdown()停止模块释放资源CanTp_Transmit(TxPduId, PduInfoPtr)发起 N-SDU 传输请求异步只用 SduLengthCanTp_CancelTransmit(TxPduId)取消发送会话需开 CanTpTcCanTp_CancelReceive(RxPduId)取消接收会话CanTp_ChangeParameter(id, parameter, value)改接收侧 BS / STmin需开 CanTpChangeParameterApiCanTp_ReadParameter(id, parameter, valuePtr)读当前 BS / STmin需开 CanTpReadParameterApi并发能力逐版本 SWS 的写法有出入这里不逐条标可重入配置时对着所用版本的 API 表核对判断依据是各 API 是否带 PduId 参数、操作的是共享通道还是私有连接。9.2 CanTp 提供给下层CanIf 调用的回调回调触发源作用CanTp_RxIndication(RxPduId, PduInfoPtr)CanIf收到一帧触发 PCI 解析与状态机CanTp_TxConfirmation(TxPduId, result)CanIf一帧发完的确认结果 E_NOT_OK 也终止会话9.3 CanTp 调用的上层回调PduR 提供回调时机PduR_CanTpStartOfReception(...)收到 SF/FF向上层申请接收缓冲PduR_CanTpCopyRxData(...)每收到一帧数据拷入上层缓冲PduR_CanTpRxIndication(...)整个 N-SDU 接收完成或异常终止PduR_CanTpCopyTxData(...)每发一帧前从上层拉数据PduR_CanTpTxConfirmation(...)整个 N-SDU 发送完成或异常终止10. C 模拟20 字节长报文的接收与流控握手下面这段代码模拟接收端收到 20 字节长报文的全过程FF 进来申请缓冲、回FC(CTS)、逐帧收 CF 校验 SN、收满后通知上层。可以直接编译运行看输出。#includestdio.h#includestdint.h#includestring.htypedefuint16_tPduIdType;typedefuint16_tPduLengthType;typedefuint8_tStd_ReturnType;#defineE_OK0x00U#defineE_NOT_OK0x01Utypedefenum{BUFREQ_OK,BUFREQ_E_NOT_OK,BUFREQ_E_BUSY,BUFREQ_E_OVFL}BufReq_ReturnType;typedefstruct{uint8_t*SduDataPtr;PduLengthType SduLength;}PduInfoType;/* 模拟上层 PduR/DCM 的接收缓冲 */staticuint8_tg_UpperLayerBuffer[128];staticPduLengthType g_RxBufferOffset0;/* ---- PduR 侧的三个回调 ---- */BufReq_ReturnTypePduR_CanTpStartOfReception(PduIdType id,constPduInfoType*info,PduLengthType TpSduLength,PduLengthType*bufferSizePtr){printf( [PduR] StartOfReception: total %u bytes.\n,(unsigned)TpSduLength);if(TpSduLengthsizeof(g_UpperLayerBuffer)){printf( [PduR] too long - BUFREQ_E_OVFL (CanTp 将回 FC(OVFLW))\n);returnBUFREQ_E_OVFL;}g_RxBufferOffset0;*bufferSizePtrsizeof(g_UpperLayerBuffer);returnBUFREQ_OK;}BufReq_ReturnTypePduR_CanTpCopyRxData(PduIdType id,constPduInfoType*info,PduLengthType*bufferSizePtr){printf( [PduR] CopyRxData: %u bytes - offset %u\n,(unsigned)info-SduLength,(unsigned)g_RxBufferOffset);memcpy(g_UpperLayerBuffer[g_RxBufferOffset],info-SduDataPtr,info-SduLength);g_RxBufferOffsetinfo-SduLength;*bufferSizePtrsizeof(g_UpperLayerBuffer)-g_RxBufferOffset;/* 每次回填剩余空间 */returnBUFREQ_OK;}voidPduR_CanTpRxIndication(PduIdType id,Std_ReturnType result){printf( [PduR] RxIndication: %s\n,resultE_OK?E_OK:E_NOT_OK);}/* 模拟 CanIf 发送这里只发流控帧 */Std_ReturnTypeCanIf_Transmit(PduIdType txPduId,constPduInfoType*pduInfo){uint8_tfspduInfo-SduDataPtr[0]0x0F;uint8_tbspduInfo-SduDataPtr[1];uint8_tstminpduInfo-SduDataPtr[2];printf( [CanIf] FC on bus - FS%u, BS%u, STmin%u\n,(unsigned)fs,(unsigned)bs,(unsigned)stmin);returnE_OK;}/* ---- CanTp 接收状态机简化版 ---- */typedefenum{CANTP_RX_IDLE,CANTP_RX_WAIT_CF}CanTp_RxStateType;staticCanTp_RxStateType CanTp_RxStateCANTP_RX_IDLE;staticPduLengthType g_TotalLen0;staticPduLengthType g_ReceivedLen0;staticuint8_tg_ExpectedSN1;voidCanTp_RxIndication(PduIdType rxPduId,constPduInfoType*pduInfo){uint8_tpciType(pduInfo-SduDataPtr[0]0xF0)4;if(pciType0x1){/* ---- First Frame ---- */g_TotalLen((PduLengthType)(pduInfo-SduDataPtr[0]0x0F)8)|pduInfo-SduDataPtr[1];printf(\n FF: total %u bytes\n,(unsigned)g_TotalLen);PduLengthType avail0;BufReq_ReturnType retPduR_CanTpStartOfReception(rxPduId,NULL,g_TotalLen,avail);if(retBUFREQ_OK){/* FF 自带 6 字节数据Classic CAN先拷走 */PduInfoType chunk{.SduDataPtrpduInfo-SduDataPtr[2],.SduLengthpduInfo-SduLength-2};PduR_CanTpCopyRxData(rxPduId,chunk,avail);g_ReceivedLenchunk.SduLength;/* 回 FC(CTS)0x30 | FS0, BS0, STmin10ms后面是填充 */uint8_tfc[8]{0x30,0x00,0x0A,0x55,0x55,0x55,0x55,0x55};PduInfoType fcPdu{.SduDataPtrfc,.SduLength8};CanIf_Transmit(rxPduId,fcPdu);CanTp_RxStateCANTP_RX_WAIT_CF;g_ExpectedSN1;}elseif(retBUFREQ_E_OVFL){/* 实际模块这里回 FC(OVFLW)0x32 */printf( would send FC(OVFLW), abort.\n);}/* BUFREQ_E_NOT_OK什么都不发静默终止 */}elseif(pciType0x2){/* ---- Consecutive Frame ---- */uint8_tsnpduInfo-SduDataPtr[0]0x0F;printf(\n CF: SN%u (expected %u)\n,(unsigned)sn,(unsigned)g_ExpectedSN);if(CanTp_RxStateCANTP_RX_WAIT_CFsng_ExpectedSN){PduLengthType copyLenpduInfo-SduLength-1;PduLengthType remainingg_TotalLen-g_ReceivedLen;if(copyLenremaining){copyLenremaining;/* 最后一帧只拷剩余长度填充字节不上去 */}PduInfoType chunk{.SduDataPtrpduInfo-SduDataPtr[1],.SduLengthcopyLen};PduLengthType avail0;PduR_CanTpCopyRxData(rxPduId,chunk,avail);g_ReceivedLencopyLen;g_ExpectedSN(g_ExpectedSN1)%16;/* 15 之后翻回 0 */if(g_ReceivedLeng_TotalLen){printf( done: %u/%u bytes\n,(unsigned)g_ReceivedLen,(unsigned)g_TotalLen);PduR_CanTpRxIndication(rxPduId,E_OK);CanTp_RxStateCANTP_RX_IDLE;}}/* SN 不对实际模块这里终止接收并通知 E_NOT_OK */}}intmain(void){/* 模拟一条 20 字节的 UDS 响应FF 带 6 字节 CF1 带 7 CF2 带 7 */uint8_tffData[8]{0x10,0x14,0x62,0xF1,0x90,0x41,0x42,0x43};/* FF_DL 0x014 20 */uint8_tcf1Data[8]{0x21,0x44,0x45,0x46,0x47,0x48,0x49,0x4A};/* SN1 */uint8_tcf2Data[8]{0x22,0x4B,0x4C,0x4D,0x4E,0x4F,0x50,0x51};/* SN2 */PduInfoType ff{.SduDataPtrffData,.SduLength8};PduInfoType cf1{.SduDataPtrcf1Data,.SduLength8};PduInfoType cf2{.SduDataPtrcf2Data,.SduLength8};CanTp_RxIndication(1,ff);CanTp_RxIndication(1,cf1);CanTp_RxIndication(1,cf2);printf(\n[App] reassembled: );for(unsignedi0;ig_RxBufferOffset;i){printf(%02X ,g_UpperLayerBuffer[i]);}printf(\n);return0;}真实工程里别自己手写这套逻辑——配置工具生成的CanTp_PBcfg.c才跟工程配置对得上这段代码的价值是把第 4、5 章的握手流程跑给你看。把StartOfReception的返回值改成BUFREQ_E_OVFL就能看到溢出分支的行为。11. 常见问题排查现象可能的原因怎么查刷写时 CF 发几帧就超时中断N_Cr 配置没把 STmin 算进去FC 里的 STmin 是 16ms而CanTpNcr只配了 10ms每帧 CF 间隔都超过 N_Cr抓 FC 帧看 STmin 原始值注意 0x00~0x7F 是 ms、0xF1~0xF9 是 100µs核对接收端 CanTpNcr必须明显大于 STmin 加总线仲裁延迟并发诊断链路互相打断两个 N-SDU 挂在同一个CanTpChannel上。同通道忙时新 FF 会按规范抢占当前接收被中断的会话以 E_NOT_OK 收场检查CanTpChannel分配物理请求 vs 功能请求、不同总线的链路要各配独立通道DET 报CANTP_E_TX_COM/CANTP_E_RX_COM时先查通道占用最后一帧 CF 收到却报CANTP_E_PADDING整轮会话失败本端配了CanTpRxPaddingActivation CANTP_ON但对端发的最后一帧 CF 没补齐 8 字节对齐两边的 Padding 配置开了填充的一方必须用填充字节补满 8 字节再发发送侧 N_Bs 超时等不到 FC对端没回 FC可能是寻址格式配错本端发 Extended 对端按 Standard 解析也可能本端把多帧发到了功能寻址通道被对端忽略抓总线看 FF 发出去后对端有没有回帧核对两侧CanTpAddressingFormat与 NSdu 的 TaType12. 小结CanTp 是 PduR 与 CanIf 之间的 ISO 15765-2 实现分片、重组、流控、超时监控。上层只管递长度和出缓冲区。四种帧SF 单帧走完FF 报总长、要缓冲CF 带序号续传FC 由接收方定节奏FS/BS/STmin。缓冲区是上层的StartOfReception 申请、CopyRxData 逐片拷、收完 RxIndication装不下回 FC(OVFLW) 终止。六个定时器A 管单帧传输、B 管流控往返、C 管续传节奏WFTmax 限制 WAIT 次数。超时动作统一是终止会话 上层 E_NOT_OK。配置三处坑STmin/Bs 挂 RxNSduPadding 两边必须一致功能寻址只许 SF。排查长报文问题先看 FC 参数和 N_* 定时器再看通道分配。
返回列表