大家好,我是左工,在上一节Aurix英飞凌TC334芯片GPT模块定时器配置中我们已经运用GPT模块实现对板载LED灯珠的亮灭控制。今天我们来聊聊嵌入式控制中非常非常关键的CAN通讯模块。

前言

在汽车电子系统中,CAN 通讯模块(Controller Area Network,控制器局域网模块)是实现车载各电子单元之间数据交互的核心组件。汽车上分布着大量电子控制单元,比如发动机ECU、变速箱TCU、ABS控制器、车身控制模块BCM、仪表盘等。CAN通讯模块为这些电子控制单元提供了统一的通信总线,让它们可以共享数据。调试过程中,我们也会用到CAN通讯外发调试信息。另外,在汽车电子系统中,CCP(CAN Calibration Protocol)协议,XCP(Universal Measurement and Calibration Protocol)协议,UDS(Unified Diagnostic Services)协议都可以通过CAN通讯实现的,因此CAN模块非常重要。


一、代码编写

利用文章“搭建英飞凌Aurix系列TC334芯片开源免费开发工具链”中所描述的方法创建一个名为“3_CanCom”的工程,如下图所示。
在这里插入图片描述
工程创建完成后,我们会在导引栏中看见如下文件和文件夹。其中文件“Cpu0_Main.c”就是我们本次需要操作的文件。
在这里插入图片描述
我们双击打开该文件,找到主函数“core0_main”,TC334运行后,将首先运行该函数。我们可以看见该函数里面有一个while循环。我们将把代码写在这个while循环附近。
在这里插入图片描述
用如下代码替换上图中被红色框标出的while循环。

    GPTimer_init();//GPT模块初始化
    CAN_init(); // CAN模块初始化
    uint8 tx_buf[8]; // 发送缓存
    while(1)
    {
        if(TimeCount>1)
        {
            TimeCount = 0;
            tx_buf[0] = rx_buf[0]; // 接受的报文第0字节发送出来
            tx_buf[1] = tx_buf[1] + 1; // 自增1
            CAN_send(0x123,tx_buf,8); // 报文发送
        }
    }

添加完成后,我们本次实验对主函数的所有操作就结束了。下面我们来添加一些必要头文件。

#include "IfxGpt12.h"
#include "IfxSrc.h"
#include "IfxCan_Can.h"

函数“GPTimer_init()”的定义可以从文章Aurix英飞凌TC334芯片GPT模块定时器配置中查看。我们只更改定时器中断回调函数即可。在回调函数中我们设定了一个自增的变量。

IFX_INTERRUPT(GPTimer_isr,0,10);  //声明定时中断服务函数
void GPTimer_isr(void)
{
     TimeCount++;
}

我们在“GPTimer_init()”的下面添加添加上“CAN_init()”函数的具体定义以及CAN模块设定需要的变量。

/************************CAN模块定义************************/
volatile uint16 rx_id = 0;  //存储接收报文ID
volatile uint8 rx_len = 0;  //存储接收报文负载数据长度
volatile uint8 rx_buf[8] = {0};  //报文接收数据
/*CAN模块初始化*/
IfxCan_Can can_handle = {0};  //CAN模块的操作句柄
IfxCan_Can_Node node0_handle = {0};  //CAN第0节点的操作句柄
/*CAN模块初始化函数*/
void CAN_init(void)
{
     /*模块配置*/
    IfxCan_Can_Config can_config = {0};  //模块配置参数结构体变量
    IfxCan_Can_initModuleConfig(&can_config,&MODULE_CAN0);  //填充CAN模块配置参数结构体
    IfxCan_Can_initModule(&can_handle,&can_config);  //初始化CAN模块
    /*端口配置*/
    IfxCan_Node_initTxPin(&IfxCan_TXD00_P20_8_OUT,IfxPort_OutputMode_pushPull,IfxPort_PadDriver_cmosAutomotiveSpeed1);
    IfxCan_Node_initRxPin(&MODULE_CAN0.N[IfxCan_NodeId_0],&IfxCan_RXD00B_P20_7_IN,IfxPort_InputMode_noPullDevice,IfxPort_PadDriver_cmosAutomotiveSpeed1);
    /*节点0配置*/
    IfxCan_Can_NodeConfig node_config = {0};
    IfxCan_Can_initNodeConfig(&node_config,&can_handle);  //填充节点默认配置
    node_config.nodeId = IfxCan_NodeId_0;  //要配置的节点为第0号节点
    node_config.frame.type = IfxCan_FrameType_transmitAndReceive;  //允许节点能接收和发送数据帧
    node_config.baudRate.baudrate = 500000;//波特率
    /*节点接收中断*/
    node_config.interruptConfig.messageStoredToDedicatedRxBufferEnabled = TRUE;  //使能接收成功中断
    node_config.interruptConfig.reint.interruptLine = IfxCan_InterruptLine_0;  //中断信号映射到0号中断线
    node_config.interruptConfig.reint.priority = 11;  //接收成功中断的优先级设为11
    IfxCan_Can_initNode(&node0_handle,&node_config);  //完成节点配置
    /*节点0过滤配置*/
    IfxCan_Filter node0_filter0 = {0};
    node0_filter0.number = 0;  //配置第0号过滤器
    node0_filter0.elementConfiguration = IfxCan_FilterElementConfiguration_storeInRxBuffer;  //过滤通过存到指定缓冲区
    node0_filter0.id1 = 0x010;  //在指定缓冲区模式下,配置id1为过滤id
    node0_filter0.rxBufferOffset = IfxCan_RxBufferId_0;  //过滤完成后存入0号接收缓冲区
    IfxCan_Can_setStandardFilter(&node0_handle,&node0_filter0);  //完成过滤器配置
}

在“CAN_init()”函数后面我们添加上CAN接收中断的回调函数“CAN_rx_isr()”的定义。这个中断回调函数发生在接收到报文后。

/*CAN模块的接收中断处理函数*/
IFX_INTERRUPT(CAN_rx_isr,0,11);  //声明CAN接收中断服务函数
void CAN_rx_isr(void)
{
    IfxCan_Message rx_msg = {0};
    uint32 data_buf[2] = {0};  //存数据的临时缓冲区
    IfxCan_Node_clearInterruptFlag(node0_handle.node,IfxCan_Interrupt_messageStoredToDedicatedRxBuffer);  //清除接收完成中断
    rx_msg.bufferNumber = IfxCan_RxBufferId_0;  //指定获取第0号接收缓冲区
    IfxCan_Can_readMessage(&node0_handle,&rx_msg,data_buf);  //读取消息,将数据存入缓冲区便于程序处理
    rx_id = (uint16)rx_msg.messageId;  //将接收到的报文ID保存下来
    rx_len = (uint8)rx_msg.dataLengthCode;  //获取接收到的数据长度
    /*取出接收到的报文数据*/
    for(uint32 cnt = 0; cnt < rx_len; cnt ++)
    {
        rx_buf[cnt] = ((uint8 *)data_buf)[cnt];  //以字节来读取数据
    }
}

在主程序中,还有一个CAN发送函数“CAN_send()”,该函数的作用就是将报文发送出去,定义如下。

/*根据报文ID发出报文,返回已完成发送的数据长度*/
void CAN_send(uint16 id,uint8 *buf,uint32 len)
{
    IfxCan_Message tx_msg = {0};
    IfxCan_Can_initMessage(&tx_msg);  //向消息结构体填充初始参数
    tx_msg.bufferNumber = IfxCan_TxBufferId_0;  //使用第0号发送缓冲区发送
    tx_msg.frameMode = IfxCan_FrameMode_standard;  //此为标准CAN协议帧
    tx_msg.dataLengthCode = len;  //设定要发送的报文的负载数据长度
    tx_msg.messageId = id;  //设置本次报文的id
    tx_msg.messageIdLength = IfxCan_MessageIdLength_standard;  //本次报文ID长度为11位的ID
    IfxCan_Can_sendMessage(&node0_handle,&tx_msg,(uint32 *)buf);  //传入负载数据启动发送
}

现在所有代码编写完毕,整个Cpu0_Main.c的定义如下所示。

#include "Ifx_Types.h"
#include "IfxCpu.h"
#include "IfxScuWdt.h"
#include "IfxGpt12.h"
#include "IfxSrc.h"
#include "IfxCan_Can.h"
IFX_ALIGN(4) IfxCpu_syncEvent g_cpuSyncEvent = 0;
/************************GPT定时模块定义************************/
uint8 TimeCount = 0;
void GPTimer_init(void)
{
    /*端口初始化*/
    IfxPort_setPinMode(&MODULE_P02,5,IfxPort_Mode_outputPushPullGeneral );  //P02.5配置为输入模式
    /*GPT120配置*/
    IfxGpt12_enableModule(&MODULE_GPT120);  //使能GPT120模块
    IfxGpt12_setGpt1BlockPrescaler(&MODULE_GPT120,IfxGpt12_Gpt1BlockPrescaler_8);  //GPT模块预分频,使用SPB总线时钟为100MHz
    /*配置T3定时器用于产生周期性中断*/
    IfxGpt12_T3_setMode(&MODULE_GPT120,IfxGpt12_Mode_timer);  //设置T3定时器的模式为定时器模式
    IfxGpt12_T3_setDirectionSource(&MODULE_GPT120,IfxGpt12_TimerDirectionSource_internal);  //内部控制计数方向
    IfxGpt12_T3_setTimerDirection(&MODULE_GPT120,IfxGpt12_TimerDirection_up);  //T3的计数方向为向上计数
    IfxGpt12_T3_setTimerPrescaler(&MODULE_GPT120,IfxGpt12_TimerInputPrescaler_128);  //定时器计数时钟为模块时钟的128分频
    IfxGpt12_T3_setTimerValue(&MODULE_GPT120,0xFFFF-50000+1);  //计数50000个脉冲即会发生溢出事件
    /*配置T3的溢出中断*/
    IfxSrc_init(&SRC_GPT120T3,IfxSrc_Tos_cpu0,10);   //配置GPT120模块的T3定时器溢出中断服务,中断优先级设为10
    IfxSrc_enable(&SRC_GPT120T3);  //使能GPT120模块的T3定时器溢出中断服务
    /*启动定时器*/
    IfxGpt12_T3_run(&MODULE_GPT120,IfxGpt12_TimerRun_start);  //启动T3
}
IFX_INTERRUPT(GPTimer_isr,0,10);  //声明定时中断服务函数
void GPTimer_isr(void)
{
     TimeCount++;
}
/************************CAN模块定义************************/
volatile uint16 rx_id = 0;  //存储接收报文ID
volatile uint8 rx_len = 0;  //存储接收报文负载数据长度
volatile uint8 rx_buf[8] = {0};  //报文接收数据
/*CAN模块初始化*/
IfxCan_Can can_handle = {0};  //CAN模块的操作句柄
IfxCan_Can_Node node0_handle = {0};  //CAN第0节点的操作句柄
/*CAN模块初始化函数*/
void CAN_init(void)
{
     /*模块配置*/
    IfxCan_Can_Config can_config = {0};  //模块配置参数结构体变量
    IfxCan_Can_initModuleConfig(&can_config,&MODULE_CAN0);  //填充CAN模块配置参数结构体
    IfxCan_Can_initModule(&can_handle,&can_config);  //初始化CAN模块
    /*端口配置*/
    IfxCan_Node_initTxPin(&IfxCan_TXD00_P20_8_OUT,IfxPort_OutputMode_pushPull,IfxPort_PadDriver_cmosAutomotiveSpeed1);
    IfxCan_Node_initRxPin(&MODULE_CAN0.N[IfxCan_NodeId_0],&IfxCan_RXD00B_P20_7_IN,IfxPort_InputMode_noPullDevice,IfxPort_PadDriver_cmosAutomotiveSpeed1);
    /*节点0配置*/
    IfxCan_Can_NodeConfig node_config = {0};
    IfxCan_Can_initNodeConfig(&node_config,&can_handle);  //填充节点默认配置
    node_config.nodeId = IfxCan_NodeId_0;  //要配置的节点为第0号节点
    node_config.frame.type = IfxCan_FrameType_transmitAndReceive;  //允许节点能接收和发送数据帧
    node_config.baudRate.baudrate = 500000;//波特率
    /*节点接收中断*/
    node_config.interruptConfig.messageStoredToDedicatedRxBufferEnabled = TRUE;  //使能接收成功中断
    node_config.interruptConfig.reint.interruptLine = IfxCan_InterruptLine_0;  //中断信号映射到0号中断线
    node_config.interruptConfig.reint.priority = 11;  //接收成功中断的优先级设为11
    IfxCan_Can_initNode(&node0_handle,&node_config);  //完成节点配置
    /*节点0过滤配置*/
    IfxCan_Filter node0_filter0 = {0};
    node0_filter0.number = 0;  //配置第0号过滤器
    node0_filter0.elementConfiguration = IfxCan_FilterElementConfiguration_storeInRxBuffer;  //过滤通过存到指定缓冲区
    node0_filter0.id1 = 0x010;  //在指定缓冲区模式下,配置id1为过滤id
    node0_filter0.rxBufferOffset = IfxCan_RxBufferId_0;  //过滤完成后存入0号接收缓冲区
    IfxCan_Can_setStandardFilter(&node0_handle,&node0_filter0);  //完成过滤器配置
}
/*根据报文ID发出报文,返回已完成发送的数据长度*/
void CAN_send(uint16 id,uint8 *buf,uint32 len)
{
    IfxCan_Message tx_msg = {0};
    IfxCan_Can_initMessage(&tx_msg);  //向消息结构体填充初始参数
    tx_msg.bufferNumber = IfxCan_TxBufferId_0;  //使用第0号发送缓冲区发送
    tx_msg.frameMode = IfxCan_FrameMode_standard;  //此为标准CAN协议帧
    tx_msg.dataLengthCode = len;  //设定要发送的报文的负载数据长度
    tx_msg.messageId = id;  //设置本次报文的id
    tx_msg.messageIdLength = IfxCan_MessageIdLength_standard;  //本次报文ID长度为11位的ID
    IfxCan_Can_sendMessage(&node0_handle,&tx_msg,(uint32 *)buf);  //传入负载数据启动发送
}
/*CAN模块的接收中断处理函数*/
IFX_INTERRUPT(CAN_rx_isr,0,11);  //声明CAN接收中断服务函数
void CAN_rx_isr(void)
{
    IfxCan_Message rx_msg = {0};
    uint32 data_buf[2] = {0};  //存数据的临时缓冲区
    IfxCan_Node_clearInterruptFlag(node0_handle.node,IfxCan_Interrupt_messageStoredToDedicatedRxBuffer);  //清除接收完成中断
    rx_msg.bufferNumber = IfxCan_RxBufferId_0;  //指定获取第0号接收缓冲区
    IfxCan_Can_readMessage(&node0_handle,&rx_msg,data_buf);  //读取消息,将数据存入缓冲区便于程序处理
    rx_id = (uint16)rx_msg.messageId;  //将接收到的报文ID保存下来
    rx_len = (uint8)rx_msg.dataLengthCode;  //获取接收到的数据长度
    /*取出接收到的报文数据*/
    for(uint32 cnt = 0; cnt < rx_len; cnt ++)
    {
        rx_buf[cnt] = ((uint8 *)data_buf)[cnt];  //以字节来读取数据
    }
}
void core0_main(void)
{
    IfxCpu_enableInterrupts();

    /* !!WATCHDOG0 AND SAFETY WATCHDOG ARE DISABLED HERE!!
     * Enable the watchdogs and service them periodically if it is required
     */
    IfxScuWdt_disableCpuWatchdog(IfxScuWdt_getCpuWatchdogPassword());
    IfxScuWdt_disableSafetyWatchdog(IfxScuWdt_getSafetyWatchdogPassword());

    /* Wait for CPU sync event */
    IfxCpu_emitEvent(&g_cpuSyncEvent);
    IfxCpu_waitEvent(&g_cpuSyncEvent, 1);
    GPTimer_init();
    CAN_init();
    uint8 tx_buf[8]; // 发送缓存
    while(1)
    {
        if(TimeCount>1)
        {
            TimeCount = 0;
            tx_buf[0] = rx_buf[0]; // 接受的报文第0字节发送出来
            tx_buf[1] = tx_buf[1] + 1; // 自增1
            CAN_send(0x123,tx_buf,8); // 报文发送
        }
    }
}

二、代码解释

下面我们就对这段代码就行简单的分析和讲解。

IfxCan_Node_initTxPin(&IfxCan_TXD00_P20_8_OUT,IfxPort_OutputMode_pushPull,IfxPort_PadDriver_cmosAutomotiveSpeed1);
IfxCan_Node_initRxPin(&MODULE_CAN0.N[IfxCan_NodeId_0],&IfxCan_RXD00B_P20_7_IN,IfxPort_InputMode_noPullDevice,IfxPort_PadDriver_cmosAutomotiveSpeed1);

上面这两行代码是将CAN的发送端口配置为P20.8端口,将接收端口配置为P20.7这与硬件原理图中的端口对应。
在这里插入图片描述

node_config.baudRate.baudrate = 500000;//波特率

这行代码的作用是将CAN的波特率设置为500kbps。波特率是指通讯速率,整个CAN网络上所有相连节点的波特率必须设置成一样。波特率越高通讯速率会越高,但是相应的通讯距离会缩短。

node_config.interruptConfig.reint.priority = 11;  //接收成功中断的优先级设为11

这行代码将CAN接收中断的优先级定为11。CAN接收中断表示,当接收到目标ID的CAN报文的时候,芯片会产生一个中断,通知处理器过来处理接收到的信息,这是CAN报文能够顺利实现信息传递的关键。

node0_filter0.id1 = 0x010;  //在指定缓冲区模式下,配置id1为过滤id

这行代码就是设定待接收的目标报文的ID。重复这个操作,可以设定多个目标报文ID。

IFX_INTERRUPT(CAN_rx_isr,0,11);  //声明CAN接收中断服务函数

这一行代码就是将优先级11的中断也就是CAN报文接收中断与中断回调函数“CAN_rx_isr()”联系起来。

void CAN_rx_isr(void)
{
    IfxCan_Message rx_msg = {0};
    uint32 data_buf[2] = {0};  //存数据的临时缓冲区
    IfxCan_Node_clearInterruptFlag(node0_handle.node,IfxCan_Interrupt_messageStoredToDedicatedRxBuffer);  //清除接收完成中断
    rx_msg.bufferNumber = IfxCan_RxBufferId_0;  //指定获取第0号接收缓冲区
    IfxCan_Can_readMessage(&node0_handle,&rx_msg,data_buf);  //读取消息,将数据存入缓冲区便于程序处理
    rx_id = (uint16)rx_msg.messageId;  //将接收到的报文ID保存下来
    rx_len = (uint8)rx_msg.dataLengthCode;  //获取接收到的数据长度
    /*取出接收到的报文数据*/
    for(uint32 cnt = 0; cnt < rx_len; cnt ++)
    {
        rx_buf[cnt] = ((uint8 *)data_buf)[cnt];  //以字节来读取数据
    }
}

我们可以看见,CAN中断的回调函数主要功能就是将接收到报文的ID、长度、内容全都放到设定好的全局变量中缓存。CAN通讯配置主要就是配置端口,波特率,报文ID信息。这段代码主要功能是周期性发送ID为0x123的报文,周期是通过GPT定时中断设定的。发送报文的第0字节为接收到ID为0x10的报文的第0个字节,发送报文的第1字节每次都自增1。程序的结构如下图所示。
在这里插入图片描述

三、下载调试

程序编写完毕后,我们点击任务栏中的编译按钮。
在这里插入图片描述
编译完成后,在输出窗口可以看见“编译完成,无错误,无警告”的字样。

在这里插入图片描述
然后我们通过Type-C将测试板与你的电脑相连。
在这里插入图片描述
点击任务栏中的下载按钮。
在这里插入图片描述
下载完成后,在输出窗口可以看见“下载成功”的字样。
在这里插入图片描述
将测试板的CAN_H(红色)和CAN_L(蓝色)接出来通过DB9接头与CAN卡相连。
在这里插入图片描述将CAN卡通过USB接口与电脑相连。
在这里插入图片描述
个人电脑的上位机我们使用TSMaster。TSMaster 是上海同星智能推出的国产汽车电子虚拟仪器软件平台,核心用于汽车总线监控仿真、诊断、标定、ECU刷写与自动化测试,兼容主流第三方硬件与工具链,且基础版免费、适合车载嵌入式与电驱动系统开发。可在其官网免费下载使用,使用方法在其官方公众号和B站账号均有详细介绍。我们这里仅使用其最基本的CAN报文收发功能。
在这里插入图片描述
配置好TSMaster后,开发板已经在周期性的发送ID为0x123的报文了,且第0字节一直在自增。我们用TSMaster发送一帧ID为0x10的报文后,我们发现0x123报文的第0字节就将接收到的0x10报文第0位0xAA转发出来了,至此我们学会了使用TC334完成了CAN的报文收发配置。


总结

今天我们介绍了如何运用TC334芯片CAN通讯模块实现了报文的收发。后续我们将继续介绍如何实现该芯片ADC采样模块的配置。敬请收藏关注,不迷路。

如需测试板,请点击。
https://store.weixin.qq.com/shop/b/feHVuMfOAmRCoHk?entrance_id=h5

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐