【AUTOSAR】CanTp(CAN 传输层) 从入门到放弃
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 字节布局
几个容易记错的点:
- SF(Classic CAN):SF_DL 在字节 0 的低 4 位,取值 1~7;用了扩展/混合寻址时首字节被地址占了,只剩 1~6。
- SF(CAN FD):数据超过 7 字节用逃逸格式——字节 0 固定
0x00,真正的长度挪到字节 1,最大 62。 - FF:12 位长度最大 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_DL=8),大于 8 才按实际长度算(SWS_CanTp_00350)——Classic CAN 时代的"短帧补齐到 8"这套约定延续到了 FD 上。
2.2 流控帧 FC 的三个字段
FC 是接收方控制发送方节奏的地方,三个字段各有分工:
- FS(流状态):
0x0CTS 授权继续发;0x1WAIT 接收端忙,让对方暂停再等一次 FC(次数受 WFTmax 限制);0x2OVFLW 缓冲区装不下,整个会话终止。 - BS(块大小):发送方连发 BS 帧 CF 后必须停下等下一个 FC。BS=0 表示剩下的 CF 一口气发完。
- STmin(最小帧间隔):两个 CF 之间的最小间隔。
0x00~0x7F按毫秒算(0~127ms);0xF1~0xF9按 100µs 算(100900µs);**保留值(0x800xF0、0xFA~0xFF)按最长的 127ms 处理**(ISO 15765-2)。
两个常见的想当然:
- STmin=0x00 不等于零间隔——它只是"没有最小间隔要求",实际节奏仍受 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 ID |
| Mixed 11-bit | 11 位 | N_AE 占首字节 | 字节 1 | 子网网关、重定向 |
| Normal Fixed(固定) | 29 位 | N_SA/N_TA 打包进 ID | 字节 0 | J1939 / SAE 诊断网络 |
| Mixed 29-bit | 29 位 | ID 打包 SA/TA,首字节放 N_AE | 字节 1 | J1939 混合多子网 |
注意:占首字节的模式里,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,封进
MetaDataPtr;CanTp 调PduR_CanTpStartOfReception/CopyRxData时原样向上透传,DCM 按元数据分流。 - 发送方向:上层调
CanTp_Transmit时把目标地址放进MetaDataPtr;CanTp 保存下来,组 SF/FF/CF/FC 下发CanIf_Transmit时回填。 - 开关:
CanTpGenericConnectionSupport,需与CanTpDynIdSupport一起打开。元数据类型是SOURCE_ADDRESS_16、TARGET_ADDRESS_16、ADDRESS_EXTENSION_8。
4. 收发数据流
CanTp 和上层之间是零拷贝的流式握手:缓冲区永远是上层的,CanTp 只负责申请、逐片搬运、最后还回。
4.1 接收侧
按这张图走一遍:
- 收到 FF,CanIf 回调
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 都不发,静默终止。
- BUFREQ_OK:分配成功,
- 每收一帧 CF,SN 校验通过后调
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 | 发送方 | CanTpNas(TxNSdu) | 请求 CanIf 发任意 N-PDU | CanTp_TxConfirmation | 终止会话 → TxConfirmation(E_NOT_OK) |
| N_Bs | 发送方 | CanTpNbs(TxNSdu) | FF(或一块最后一帧 CF)的发送确认 | 收到合法 FC | 终止发送 → TxConfirmation(E_NOT_OK) |
| N_Cs | 发送方 | CanTpNcs(TxNSdu) | 收到 FC(CTS) 或上一帧 CF 确认 | CopyTxData 备好下一帧 | 终止发送 → TxConfirmation(E_NOT_OK) |
| N_Ar | 接收方 | CanTpNar(RxNSdu) | 请求 CanIf 发送 FC | FC 的 TxConfirmation | 终止接收 → RxIndication(E_NOT_OK) |
| N_Br | 接收方 | CanTpNbr(RxNSdu) | 收到 FF 或一个 Block 的最后一帧 CF | 发出 FC | 见下方说明 |
| N_Cr | 接收方 | CanTpNcr(RxNSdu) | FC(CTS) 发出 / 收到上一帧 CF | 收到序号正确的下一帧 CF | 终止接收 → RxIndication(E_NOT_OK) |
两点说明:
- N_Br 在 ISO 里是性能要求(N_Ar + N_Br < 0.9 × N_Bs),N_Cs 同理——它们本来描述的是"两步之间的时间预算",AUTOSAR 把它们也做成了可配的定时器。这张表是六份规范资料里最容易张冠李戴的地方。
- WFTmax(CanTpWftMax,配 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_ON:SF、最后一帧 CF、FC 不足 8 字节时,用CanTpPaddingByte(常见 0x55/0xAA)补齐,CanTp 与 CanIf 之间永远按 8 字节走。 - 接收侧
CanTpRxPaddingActivation = CANTP_ON:只接受 8 字节的 SF 和最后一帧 CF。SF 短于 8 → 拒收该帧 + DETCANTP_E_PADDING(SWS_CanTp_00345);最后一帧 CF 短于 8 → 终止整个接收,RxIndication(E_NOT_OK)+ DET(SWS_CanTp_00346)——两条的后果不一样,后者砍掉的是一整轮会话。 - 接收侧配 OFF 也有长度检查:过短的帧直接忽略,只是不报 DET(SWS_CanTp_00098)。
- 两种模式下,CanTp 都只把有效数据字节传给上层(SWS_CanTp_00116)——填充字节永远不会混进上层数据,上层看到的长度由 PCI 里的 DL 决定。
7.2 功能寻址
- 发送侧:功能寻址只许单帧。想发多帧 → E_NOT_OK + DET
CANTP_E_INVALID_TATYPE。 - 接收侧:功能寻址通道上收到 FF/CF/FC 一律忽略、不回任何流控(ISO 15765-2 的规定)。
原因很直白:广播 1:N 没有唯一的接收方能回 FC(CTS),多帧握手机制根本跑不起来。要收长报文用物理寻址。
8. 配置要点
配置分三层:CanTpGeneral(全局开关)→CanTpConfig→CanTpChannel(半双工/全双工)→RxNSdu/TxNSdu。定时器和流控参数的归属要记牢:
| 参数 | 容器 | 说明 |
|---|---|---|
| CanTpNas / CanTpNbs / CanTpNcs | TxNSdu | 发送侧三个定时器 |
| CanTpNar / CanTpNbr / CanTpNcr | RxNSdu | 接收侧三个定时器 |
| CanTpSTmin / CanTpBs | RxNSdu | 写进 FC 的命令,挂在接收侧 |
| CanTpWftMax | RxNSdu | 连续 FC(WAIT) 次数上限 |
| CanTpPaddingByte | General | 填充值 |
| CanTpChannelMode | Channel | FULL_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 提供给上层的标准 API
| API | 作用 |
|---|---|
CanTp_Init(CfgPtr) | 初始化模块与所有通道状态机 |
CanTp_Shutdown() | 停止模块,释放资源 |
CanTp_Transmit(TxPduId, PduInfoPtr) | 发起 N-SDU 传输请求(异步,只用 SduLength) |
CanTp_CancelTransmit(TxPduId) | 取消发送会话(需开 CanTpTc) |
CanTp_CancelReceive(RxPduId) | 取消接收会话 |
CanTp_ChangeParameter(id, parameter, value) | 改接收侧 BS / STmin(需开 CanTpChangeParameterApi) |
CanTp_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、收满后通知上层。可以直接编译运行看输出。
#include<stdio.h>#include<stdint.h>#include<string.h>typedefuint16_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_RxBufferOffset=0;/* ---- PduR 侧的三个回调 ---- */BufReq_ReturnTypePduR_CanTpStartOfReception(PduIdType id,constPduInfoType*info,PduLengthType TpSduLength,PduLengthType*bufferSizePtr){printf(" [PduR] StartOfReception: total %u bytes.\n",(unsigned)TpSduLength);if(TpSduLength>sizeof(g_UpperLayerBuffer)){printf(" [PduR] too long -> BUFREQ_E_OVFL (CanTp 将回 FC(OVFLW))\n");returnBUFREQ_E_OVFL;}g_RxBufferOffset=0;*bufferSizePtr=sizeof(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_RxBufferOffset+=info->SduLength;*bufferSizePtr=sizeof(g_UpperLayerBuffer)-g_RxBufferOffset;/* 每次回填剩余空间 */returnBUFREQ_OK;}voidPduR_CanTpRxIndication(PduIdType id,Std_ReturnType result){printf(" [PduR] RxIndication: %s\n",result==E_OK?"E_OK":"E_NOT_OK");}/* 模拟 CanIf 发送(这里只发流控帧) */Std_ReturnTypeCanIf_Transmit(PduIdType txPduId,constPduInfoType*pduInfo){uint8_tfs=pduInfo->SduDataPtr[0]&0x0F;uint8_tbs=pduInfo->SduDataPtr[1];uint8_tstmin=pduInfo->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_RxState=CANTP_RX_IDLE;staticPduLengthType g_TotalLen=0;staticPduLengthType g_ReceivedLen=0;staticuint8_tg_ExpectedSN=1;voidCanTp_RxIndication(PduIdType rxPduId,constPduInfoType*pduInfo){uint8_tpciType=(pduInfo->SduDataPtr[0]&0xF0)>>4;if(pciType==0x1){/* ---- First Frame ---- */g_TotalLen=((PduLengthType)(pduInfo->SduDataPtr[0]&0x0F)<<8)|pduInfo->SduDataPtr[1];printf("\n>>> FF: total %u bytes\n",(unsigned)g_TotalLen);PduLengthType avail=0;BufReq_ReturnType ret=PduR_CanTpStartOfReception(rxPduId,NULL,g_TotalLen,&avail);if(ret==BUFREQ_OK){/* FF 自带 6 字节数据(Classic CAN),先拷走 */PduInfoType chunk={.SduDataPtr=&pduInfo->SduDataPtr[2],.SduLength=pduInfo->SduLength-2};PduR_CanTpCopyRxData(rxPduId,&chunk,&avail);g_ReceivedLen=chunk.SduLength;/* 回 FC(CTS):0x30 | FS=0, BS=0, STmin=10ms;后面是填充 */uint8_tfc[8]={0x30,0x00,0x0A,0x55,0x55,0x55,0x55,0x55};PduInfoType fcPdu={.SduDataPtr=fc,.SduLength=8};CanIf_Transmit(rxPduId,&fcPdu);CanTp_RxState=CANTP_RX_WAIT_CF;g_ExpectedSN=1;}elseif(ret==BUFREQ_E_OVFL){/* 实际模块这里回 FC(OVFLW):0x32 */printf(">>> would send FC(OVFLW), abort.\n");}/* BUFREQ_E_NOT_OK:什么都不发,静默终止 */}elseif(pciType==0x2){/* ---- Consecutive Frame ---- */uint8_tsn=pduInfo->SduDataPtr[0]&0x0F;printf("\n>>> CF: SN=%u (expected %u)\n",(unsigned)sn,(unsigned)g_ExpectedSN);if(CanTp_RxState==CANTP_RX_WAIT_CF&&sn==g_ExpectedSN){PduLengthType copyLen=pduInfo->SduLength-1;PduLengthType remaining=g_TotalLen-g_ReceivedLen;if(copyLen>remaining){copyLen=remaining;/* 最后一帧只拷剩余长度,填充字节不上去 */}PduInfoType chunk={.SduDataPtr=&pduInfo->SduDataPtr[1],.SduLength=copyLen};PduLengthType avail=0;PduR_CanTpCopyRxData(rxPduId,&chunk,&avail);g_ReceivedLen+=copyLen;g_ExpectedSN=(g_ExpectedSN+1)%16;/* 15 之后翻回 0 */if(g_ReceivedLen>=g_TotalLen){printf(">>> done: %u/%u bytes\n",(unsigned)g_ReceivedLen,(unsigned)g_TotalLen);PduR_CanTpRxIndication(rxPduId,E_OK);CanTp_RxState=CANTP_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};/* SN=1 */uint8_tcf2Data[8]={0x22,0x4B,0x4C,0x4D,0x4E,0x4F,0x50,0x51};/* SN=2 */PduInfoType ff={.SduDataPtr=ffData,.SduLength=8};PduInfoType cf1={.SduDataPtr=cf1Data,.SduLength=8};PduInfoType cf2={.SduDataPtr=cf2Data,.SduLength=8};CanTp_RxIndication(1,&ff);CanTp_RxIndication(1,&cf1);CanTp_RxIndication(1,&cf2);printf("\n[App] reassembled: ");for(unsignedi=0;i<g_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 的 TaType |
12. 小结
- 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 挂 RxNSdu;Padding 两边必须一致;功能寻址只许 SF。排查长报文问题先看 FC 参数和 N_* 定时器,再看通道分配。