最近基于 TC377 芯片在做 CAN 通信的集成本次只配 MCAL 层依托已有硬件相当于只做裸机开发。MCAL 的配置用的最常见的 EB 工具。一、时钟配置在 MCU 中设置 CAN 的时钟频率为 40MHz。二、CAN 驱动配置基础配置用默认的就行包括去初始化运行权限CAN 主函数周期等。本项目中共有 4 路 CAN需要配 4 个 CAN 控制器本文重点以其中一路来介绍。原理图中只给了 P00.0 用于 CAN_TXP00.1 用于 CAN_RX。到 TC37x 的 datasheet 中找到对应的 CAN_RX 引脚CAN10_RXDA。需要注意这里的 CAN10 并不是指第 10 路 CAN而是 CAN1_NODE0。从我自定义的控制器名字后缀就可以看出工程中用到的四路 CAN 分别是 CAN0_NODE0CAN1_NODE0CAN1_NODE1 和 CAN1_NODE3。再到英飞凌的 can_17_mcmcan 手册中找到 CAN10 对应的 CAN 控制器编号和地址。这里的 NODE10 对应的就是 Controller4这个编号 4 需要记住后面还用得到。具体到控制器里面需要填的内容包括processing本次报文收、发、busoff 和唤醒都采用中断触发的方式选择 INTERRUPT。CanRxInputSelection根据 datasheet 中 PIN 脚定义的选对应 CANxx_RXDA、CANxx_RXDB、CANxx_RXDC。CanControllerBaseAddress控制器地址按照 CAN 驱动手册中定义的填写。波特率需要先自己定义好波特率的配置再去引用。时钟频率这里就选默认的 CAN 时钟频率就行MCU 中配的默认值是 40MHz。我这里波特率分别配的仲裁域 500kbps80% 采样数据域 5Mbps75% 采样。具体的分频、传播段延时补偿、相位缓冲、同步补偿可以根据公式自己去凑。CanControllerPropSeg传播延时补偿CanControllerSeg1采样点之前时钟偏差补偿CanControllerSeg2采样点之后时钟偏差补偿CanControllerSyncJumpWidth同步补偿最大步长必须小于CanControllerSeg2EB 中默认时钟分频为 1。仲裁域波特率公式40000000/(1471616) 500000采样点(14716)/(1471616) 80%数据域波特率公式40000000/(1142) 5000000采样点(114)/(1142) 75%如果不勾选 CanControllerTxBitRateSwitch默认只收发 CAN 类型报文就没有数据域和仲裁域的区分都是按 500k 传输CanControllerFdBaudrateConfig 的配置就不生效。勾选之后就支持 CAN FD 报文收发数据域的波特率设置才生效。CanHardwareObject 是 MCAL 层 CAN 模块提供给 CanIf 的逻辑句柄分为接收对象和发送对象这里接收和发送引用同一个邮箱资源。注意必须先添加所有接收对象再添加所有发送对象。CanHandleTypeBASIC一个对象可以服务于多个 ID 的报文FULL一个对象只用于接收或发送特定的一个 ID 的报文我这里用的 basic可以收发任意的报文。CanIdTypemixedstandardextended分为标准帧扩展帧或者两者都有。CanObjectType传输/发送CanControllerRef索引到前面定义的控制器。CanObjectId实际CanWrite入参中的Hth就是这里定义的4,5,6,7Can_17_McmCan_Write(const Can_HwHandleType Hth, const Can_PduType *const PduInfo)接收对象内部可配置报文过滤规则ID 掩码机制CanHwObjectCount 必须大于 1 才允许编辑过滤规则。过滤逻辑为(总线上收到的 ID FilterMask) (FilterId FilterMask)即掩码中为 1 的位必须和 FilterID 对应位严格相等掩码中为 0 的位不做比对。通过该机制可精确匹配某一帧或一组报文。我这里默认接收所有 ID 的报文所以 ID 和掩码都填了 0。三、中断配置根据之前找到的 Controller 编号找到对应的中断每个 Controller 会触发 4 类中断在 IRQ 中先找到它属于 Can0 还是 Can1然后在对应的 4 个中断中填上中断优先级就使能了该中断。优先级越小优先级越高默认优先级是 0如果填 0 则不生效。英飞凌的 can_17_mcmcan中还规定了特定的中断函数定义格式参照该格式把每个 Controller 的 4 个中断实现函数名和入参手册中已经写死直接复制即可。四、ResourceM 配置为保证 CAN 触发中断后只由单一的核去处理在 ResourceM 中添加 core0在 core0 中给 CAN 分频资源。五、代码实现除了 CAN 控制器是芯片自带的外设还需要有 CAN 收发器才能跟外部 ECU 通信这里根据不同的收发器芯片操作其 STB 和 EN 引脚。我这里分别用到了 TJA1443at 和 TJA1044btk自定义了使能和去使能接口uint8 CanTrcv_SetOpMode(uint8 Transceiver, CanTrcv_TrcvModeType OpMode) { if (Transceiver CAN_TRCV_NUM) { return E_NOT_OK; } if (OpMode CANTRCV_TRCVMODE_NORMAL) { if (Transceiver TJA1044BTK_NUM) { Dio_WriteChannel(g_tja1044btkPortCfg[Transceiver].stbPortId, STD_LOW); } else { Dio_WriteChannel(g_tja1443atPortCfg.stbPortId, STD_HIGH); Dio_WriteChannel(g_tja1443atPortCfg.enPortId, STD_HIGH); } } else { if (Transceiver TJA1044BTK_NUM) { Dio_WriteChannel(g_tja1044btkPortCfg[Transceiver].stbPortId, STD_HIGH); Dio_WriteChannel(g_tja1044btkPortCfg[Transceiver].txPortId, STD_HIGH); } else { Dio_WriteChannel(g_tja1443atPortCfg.stbPortId, STD_LOW); Dio_WriteChannel(g_tja1443atPortCfg.enPortId, STD_LOW); } } return E_OK; }初始化时要同时调用中断使能、收发器的使能和控制器的启动接口。void CanApp_Init(void) { static uint8 g_canInited 0u; if (g_canInited) { return; } IrqCan_Init(); SRC_CAN_CAN0_INT0.B.SRE 1; SRC_CAN_CAN0_INT1.B.SRE 1; SRC_CAN_CAN0_INT2.B.SRE 1; SRC_CAN_CAN0_INT3.B.SRE 1; SRC_CAN_CAN1_INT0.B.SRE 1; SRC_CAN_CAN1_INT1.B.SRE 1; SRC_CAN_CAN1_INT2.B.SRE 1; SRC_CAN_CAN1_INT3.B.SRE 1; SRC_CAN_CAN1_INT4.B.SRE 1; SRC_CAN_CAN1_INT5.B.SRE 1; SRC_CAN_CAN1_INT6.B.SRE 1; SRC_CAN_CAN1_INT7.B.SRE 1; SRC_CAN_CAN1_INT12.B.SRE 1; SRC_CAN_CAN1_INT13.B.SRE 1; SRC_CAN_CAN1_INT14.B.SRE 1; SRC_CAN_CAN1_INT15.B.SRE 1; Can_17_McmCan_Init(Can_17_McmCan_Config); for (uint8 i 0; i 4; i) { CanTrcv_SetOpMode(i, CANTRCV_TRCVMODE_NORMAL); } Can_17_McmCan_SetControllerMode(Can_17_McmCanConf_CanController_CanController_CAN00, CAN_CS_STARTED); Can_17_McmCan_SetControllerMode(Can_17_McmCanConf_CanController_CanController_CAN10, CAN_CS_STARTED); Can_17_McmCan_SetControllerMode(Can_17_McmCanConf_CanController_CanController_CAN11, CAN_CS_STARTED); Can_17_McmCan_SetControllerMode(Can_17_McmCanConf_CanController_CanController_CAN13, CAN_CS_STARTED); Can_17_McmCan_EnableControllerInterrupts(Can_17_McmCanConf_CanController_CanController_CAN00); Can_17_McmCan_EnableControllerInterrupts(Can_17_McmCanConf_CanController_CanController_CAN10); Can_17_McmCan_EnableControllerInterrupts(Can_17_McmCanConf_CanController_CanController_CAN11); Can_17_McmCan_EnableControllerInterrupts(Can_17_McmCanConf_CanController_CanController_CAN13); g_canInited 1u; return; }自测试代码如下uint8 CanApp_Test(void) { uint8 ret 0; Can_PduType txPdu; txPdu.swPduHandle 0; txPdu.id 0x123; txPdu.length 8; txPdu.sdu g_canRxdata1; if (Can_Write(Can_17_McmCanConf_CanHardwareObject_CanHardwareObject_CANFD10, txPdu) ! E_OK) { ret 1; } Can_ControllerStateType ControllerMode1 0; Can_17_McmCan_GetControllerMode(1, ControllerMode1); if (ControllerMode1 ! CAN_CS_STARTED) { ret 2; } Can_ErrorStateType ErrorState1 0; Can_17_McmCan_GetControllerErrorState(1, ErrorState1); if (ErrorState1 ! CAN_ERRORSTATE_ACTIVE) { ret 3; } Mcal_GptDelayUs(1000); Can_PduType txpdu1; txpdu1.swPduHandle 0; txpdu1.id 0xC1234567; txpdu1.length 16; txpdu1.sdu g_canRxdata2; if (Can_Write(Can_17_McmCanConf_CanHardwareObject_CanHardwareObject_CANFD10, txpdu1) ! E_OK) { ret 4; } Mcal_GptDelayUs(1000); return ret; }模拟不同类型报文发送扩展帧canid的第31位设为1标准帧canid的第31位设为0can2.0canid的第30位设为0canfdcanid的第30位设为1测试代码中canid设为0xC1234567就是模拟发送canfd扩展帧。问题一这里自验证的时候发现一个现象同一周期中第二次调用Can_Write报文始终发不出来。后来打断点发现首次调用Can_Write周立功虽然接收到了报文但TransmitHandle中断回调始终没有触发直到本轮任务调度结束才触发。由于没有触发中断Tx buffer没有释放第二次Can_Write时判断条件不满足所以返回了发送失败。究其原因当前工程的10ms任务调度是用STM实现的本身整个10ms任务就是在STM的中断回调里运行STM的中断优先级自然是最高的在高优先级的中断里面无法再去触发低优先级中断所以只能等10ms任务退出时才会触发TransmitHandle。将测试代码放到主函数的while1里面就可以实现同时发送多帧报文了。问题二整个MCAL配置过程中容易绕进去的就是CAN10对应CAN1_NODE0再对应Controller4这部分要结合多个手册去一一对应浪费了不少时间。六、总结本文详细介绍了基于 TC377 芯片的 CAN 通信 MCAL 层配置全过程主要包括以下几个关键步骤时钟配置设置 CAN 时钟频率为 40MHz为后续波特率计算提供基础。CAN 驱动配置包括控制器配置、引脚映射、波特率设置仲裁域 500kbps数据域 5Mbps、硬件对象定义及报文过滤规则配置。中断配置根据控制器编号配置对应的中断优先级实现报文收发、busoff 和唤醒的中断处理。ResourceM 配置确保 CAN 中断由单一核处理避免资源冲突。代码实现提供了收发器控制、初始化及自测试代码完整实现了 CAN 通信功能。通过以上配置可以在 TC377 平台上实现稳定可靠的 CAN 通信支持标准 CAN 和 CAN FD 协议。实际应用中需根据具体硬件连接调整引脚配置并根据通信需求优化波特率和过滤规则。