Microchip 32位MCU CAN驱动图文教程-附源码

本文详细介绍了如何在MCC环境下创建一个新的32位MCU工程,包括配置系统时钟、添加调试打印模块、CAN模块设置以及管脚功能的管理,还涉及堆栈大小的调整和用户代码的编写,重点展示了CAN模块的工作模式和数据帧过滤规则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

创建一个新的32位MCU工程

确保电脑上已经安装最新的MPlab X IDE、XC32编译器和MCC harmony软件仓库,比如当前所使用的MPlab X IDE版本是V6.20

  • 打开MPlab X IDE后,选择File->New Project:
    在这里插入图片描述
  • 选择Application Project,点击下一步:
    在这里插入图片描述
  • 选择需要选择的器件型号,比如本教程选用的ATSAME54P20 MCU,点击下一步:
    在这里插入图片描述
  • 选择交叉编译工具,点击下一步:
    在这里插入图片描述
  • 输入工程名称和选择存储路径,最后点击完成Finish
    在这里插入图片描述
  • 需要确保本地电脑上已经下载了Microchip MCC Harmony针对于所使用MCU型号的软件包,可以从github或者国内的gitee进行下载,并在MPlab X IDE里面进行配置。
    • 配置过程可以通过Tools -> Option进入
      在这里插入图片描述

Microchip MCC Harmony配置界面说明

Microchip MCC harmony提供图形化的配置界面,能够方便的增减各种外设驱动库、中间件,提供图形化的系统时钟、管脚、DMA、事件系统和网络协议栈的配置,当完成配置后能够一键生成代码。
在这里插入图片描述

  • 在官方SAME54 Xplained Pro开发板上完成以下配置目标
    • 采用外部12MHz晶振输入
    • 提供40M clock给CAN模块
    • CAN工作在500kbps速率,采样点为75%
    • 使用RX FIFO0来接收标准帧,FIFO1接收扩展帧
    • 标准帧过滤器(Classic、Range模式)设置参考(只接收ID为0x1D00x1D7和0x1F00x1F8的帧)
    • 扩展帧过滤器(Classic、Range模式)设置参考(只接收ID为0x1FFF1240~0x1FFF1248的帧)

在MCC下配置系统的时钟

在Plugins里面打开Clock Configuration
在这里插入图片描述
打开时钟配置界面后,将看到以下的配置界面:
在这里插入图片描述
外部无源晶振输入的配置(外部12MHz晶振输入接在XOSC1上):
在这里插入图片描述
XOSC CTRL配置的高级选项:
在这里插入图片描述
MCU通用时钟发生器1~4的配置:
在这里插入图片描述
通用时钟发生器GCLK2的配置:
在这里插入图片描述
通用时钟发生器GCLK3的配置:
在这里插入图片描述
MCU锁相环 FDPLL0的配置:
在这里插入图片描述
FDPLL0的高级配置:
在这里插入图片描述
外设时钟的配置:
在这里插入图片描述

在MCC下配置所需要使用的模块

配置调试打印模块

添加SERCOM2模块,选择MCC Resource Management -> Device Resources -> Libraries -> Harmony -> Peripherals -> SERCOM -> SERCOM2
在这里插入图片描述
配置SERCOM2模块,在Project Graph界面下左键单击新添加的SERCOM2模块
在这里插入图片描述
一个SERCOM模块有4个PAD,PAD0~3,参考SAME54开发板原理图,打印用的串口PAD1为RX,PAD0为TX,所以配置SERCOM2的Receive/Transmit Pinout需要和原理图保持一致
在这里插入图片描述
添加STDIO模块,选择MCC Resource Management -> Device Resources -> Libraries -> Harmony -> Tools -> STDIO
在这里插入图片描述
配置STDIO模块
在这里插入图片描述
将STDIO和SERCOM2关联起来,右键点击STDIO组件下的粉色边框,选择Satisfiers下的SERCOM2即可
在这里插入图片描述
STDIO标准打印接口绑定到SERCOM2,STDIO调试信息将输出到SERCOM2口
在这里插入图片描述

配置CAN模块

添加CAN1模块,选择MCC Resource Management -> Device Resources -> Libraries -> Harmony -> Peripherals -> CAN -> CAN1
在这里插入图片描述

  • 对添加的CAN1模块进行配置:
    • 满足过滤规则的帧将存储在指定的FIFO中
    • 提供40M clock给CAN模块
    • CAN工作在500 kbps速率,采样点为75%
    • 使用RX FIFO0来接收标准帧,FIFO1接收扩展帧
    • 如果需要将CAN工作速率修改为250 kbps,只需Bit Rate手动输入250即可
      在这里插入图片描述
      在这里插入图片描述
  • CAN1模块标准帧过滤器设置规则如下:
    • 满足过滤规则的标准帧将存储在指定的RX FIFO0中
    • 只接收帧ID在0x1D0 ~ 0x1D7范围内的帧(过滤器0用Classic方式实现)
    • 只接收帧ID在0x1F0 ~ 0x1F8范围内的帧(过滤器1用Range方式实现)
  • CAN1模块扩展帧过滤器设置规则如下:
    • 满足过滤规则的扩展帧将存储在指定的RX FIFO1中
    • 只接收帧ID在0x1FFF1230 ~ 0x1FFF1237范围内的帧(过滤器0用Classic方式实现)
    • 只接收帧ID在0x1FFF1240 ~ 0x1FFF1248范围内的帧(过滤器1用Range方式实现)
      在这里插入图片描述
      在这里插入图片描述

配置管脚功能

配置STDIO打印用到的SERCOM口TX、RX管脚,CAN1模块用到的CAN1_TX、CAN1_RX和CAN收发器standby控制管脚PC13
在这里插入图片描述
在Project Graph界面下,打开Pin Configuration
在这里插入图片描述
随后在Pin Setting下对所需要的管脚进行配置
在这里插入图片描述

修改系统堆栈大小

从Project Graph界面下,点击System模块,从右边配置树中修改Heap Size
在这里插入图片描述

生成代码

在这里插入图片描述

添加用户代码

在main.c文件下,首先添加PLIB CAN驱动需要用到CAN消息缓存buffer和变量的定义。很多变量需要在回调函数中使用,而回调函数是在中断处理代码中被调用,因此需要定义为volatile类型,参考代码如下图所示:

/* CAN message storage buffer definition */
uint8_t Can1MessageRAM[CAN1_MESSAGE_RAM_CONFIG_SIZE] __attribute__((aligned (32)));

/* Standard identifier id[28:18]*/
#define WRITE_ID(id) (id << 18)
#define READ_ID(id)  (id >> 18)

//static uint8_t g_txFiFo[MCAN1_TX_FIFO_BUFFER_SIZE]; /* CAN TX message buffer */
static uint8_t g_rxFiFo0[CAN1_RX_FIFO0_SIZE];      /* CAN FIFO 0 RX buffer */
static uint8_t g_rxFiFo1[CAN1_RX_FIFO1_SIZE];      /* CAN FIFO 1 RX buffer */

static volatile bool    g_txdone  = false;         /* CAN TX completion flag */
static volatile bool    g_rx0done = false;         /* FIFO 0 got new message */
static volatile bool    g_rx1done = false;         /* FIFO 1 got new message */
static volatile uint8_t g_rxnum0 = 0;              /* FIFO 0 new message number */
static volatile uint8_t g_rxnum1 = 0;              /* FIFO 0 new message number */

随后定义CAN PLIB驱动中用到的RX FIFOx接收完成回调函数和TX FIFO发送完成回调函数,其中使用RX FIFO0用于存储标准帧,RX FIFO1用于存储扩展帧。需要在CAN驱动初始化的时候注册FIFO发送完成和接收完成的回调函数。需要注意的是,回调函数是在中断上下文中执行:

static void CAN1_TXFIFO_Txdone(uintptr_t contextHandle)
{
    g_txdone = true;
}

static void CAN1_RXFIFO0_Rxdone(uint8_t numberOfMessage, uintptr_t contextHandle)
{
    g_rx0done = true;
    g_rxnum0  = numberOfMessage;
}

static void CAN1_RXFIFO1_Rxdone(uint8_t numberOfMessage, uintptr_t contextHandle)
{
    g_rx1done = true;
    g_rxnum1  = numberOfMessage;
}

在CAN驱动初始化的时候需要调用GPIO PC13的clear操作,用于将CAN收发器ATA6561跳出standby模式。同时提供一个打印接收的CAN数据帧的函数,用来观察收到的CAN数据帧。打印函数的参数定义如下:
.fifonum – CAN RX FIFO通道号:
.numberofMessage——接收的消息数量:
.rxBuf——接收消息的缓存区首地址:
.rxBufLen——单条消息缓存区的长度:

static inline void CAN1_Demo_Initialization(void)
{
    GPIO_PC13_Clear();
    CAN1_TxFifoCallbackRegister(CAN1_TXFIFO_Txdone, 0);
    CAN1_RxFifoCallbackRegister(CAN_RX_FIFO_0, CAN1_RXFIFO0_Rxdone, 0);
    CAN1_RxFifoCallbackRegister(CAN_RX_FIFO_1, CAN1_RXFIFO1_Rxdone, 0);
}

/* Print Rx Message */
static void print_message(CAN_RX_FIFO_NUM fifonum, uint8_t numberOfMessage, 
                          CAN_RX_BUFFER *rxBuf, uint8_t rxBufLen)
{
    uint8_t msgLength = 0;
    uint32_t id = 0;

    for (uint8_t count = 0; count < numberOfMessage; count++)
    {
        /* Print message to Console */
        printf(" Rx FIFO%d: ", 
                fifonum == CAN_RX_FIFO_0 ? 0:1);
        id = rxBuf->xtd ? rxBuf->id : READ_ID(rxBuf->id);
        msgLength = rxBuf->dlc;
        printf(" Message - ID=0x%x Length=%d\r\n", (unsigned int)id, (unsigned int)msgLength);
        rxBuf += rxBufLen;
    }
}

在while(1)主循环中添加以下代码,判断RX FIFOx是否有接收到新的数据帧,如果有则清除标记位并记录当前收到的帧数,需要注意的是加入临界区保护代码。读取CAN数据帧时,先将用户帧缓存内容清零,然后从FIFO中读取指定数量的帧到缓存区,最后打印接收的帧内容:

int main ( void )
{
    uint8_t rx_num;
    
    /* Initialize all modules */
    SYS_Initialize ( NULL );

    printf(" ------------------------------ \r\n");
    printf("            CAN Demo            \r\n");
    printf(" ------------------------------ \r\n");
    
    /* Set Message RAM Configuration */
    CAN1_MessageRAMConfigSet(Can1MessageRAM);
    
    CAN1_Demo_Initialization();
    
    while ( true )
    {
        /* Maintain state machines of all polled MPLAB Harmony modules. */
        SYS_Tasks ( );

        if (g_rx0done)
        {
            __disable_irq();
            g_rx0done = false;
            rx_num    = g_rxnum0;
            __enable_irq();
            
            memset(g_rxFiFo0, 0x00, (rx_num * CAN1_RX_FIFO0_ELEMENT_SIZE));
            if (CAN1_MessageReceiveFifo(CAN_RX_FIFO_0, rx_num, 
                                        (CAN_RX_BUFFER *)g_rxFiFo0) == true)
            {
                print_message(CAN_RX_FIFO_0, rx_num, (CAN_RX_BUFFER *)g_rxFiFo0,
                              CAN1_RX_FIFO0_ELEMENT_SIZE);
            }
            else
            {
                printf(" Error in FIFO0 received message\r\n");
            }
        }
        
        if (g_rx1done)
        {
            __disable_irq();
            g_rx1done = false;
            rx_num    = g_rxnum1;
            __enable_irq();
            
            memset(g_rxFiFo1, 0x00, (rx_num * CAN1_RX_FIFO1_ELEMENT_SIZE));
            if (CAN1_MessageReceiveFifo(CAN_RX_FIFO_1, rx_num, (CAN_RX_BUFFER *)g_rxFiFo1) == true)
            {
                print_message(CAN_RX_FIFO_1, rx_num, (CAN_RX_BUFFER *)g_rxFiFo1,
                              CAN1_RX_FIFO1_ELEMENT_SIZE);
            }
            else
            {
                printf(" Error in FIFO1 received message\r\n");
            }
        }
    }

    /* Execution should not come here during normal operation */

    return ( EXIT_FAILURE );
}
### MCU CAN 驱动实现方法 对于MCU上的CAN驱动实现,特别是针对NXP S32K1系列微控制器而言,在C++环境中可以通过继承抽象基类`ICAN`并利用官方提供的SDK来完成具体的实现[^1]。 #### 设计模式与接口定义 为了提高代码的可维护性和扩展性,推荐采用面向对象的设计原则。可以创建一个名为`CCAN`的具体类,该类实现了由`ICAN`所声明的方法。这不仅有助于分离硬件层逻辑与其他应用层面的功能,还便于后续测试和调试工作。 ```cpp class ICAN { public: virtual ~ICAN() {} virtual void Init(uint32_t baudrate) = 0; virtual bool Send(const uint8_t* data, size_t length) = 0; virtual bool Receive(uint8_t* buffer, size_t& length) = 0; }; class CCAN : public ICAN { private: // 私有成员变量用于存储配置参数等信息 protected: // 受保护函数可用于派生类访问底层资源操作 public: explicit CCAN(/* 初始化所需参数 */); void Init(uint32_t baudrate) override; // 初始化设置 bool Send(const uint8_t* data, size_t length) override; // 发送消息 bool Receive(uint8_t* buffer, size_t& length) override; // 接收消息 }; ``` #### 使用NXP SDK初始化CAN模块 当涉及到实际硬件交互部分时,则依赖于制造商所提供的软件开发包(SDK),比如这里提到的是NXP公司的S32 Design Studio配套工具链及其带的标准外设库文件。这些API允许开发者轻松地配置通信波特率、过滤器以及其他必要的属性而无需深入了解寄存器级细节。 ```c // 假定已经包含了相应的头文件<sdk_headers.h> void CCAN::Init(uint32_t baudrate){ can_config_t config; /* 获取默认配置 */ CAN_GetDefaultConfig(&config); /* 设置自定义参数 */ config.baudRate_Bps = baudrate; /* 应用新的配置到指定通道 */ (void)CAN_Init(CAN_PERIPHERAL_BASEADDR, &config); } ``` #### 数据传输功能实现 发送接收数据的过程同样遵循类似的思路——即先准备好待处理的数据帧结构体,再调用相应服务例程完成最终的任务执行。需要注意的是,由于实时操作系统环境下的多任务调度特性可能会影响程序行为,因此建议在适当置加入同步机制以保障通讯过程的安全可靠。 ```c++ bool CCAN::Send(const uint8_t *data, size_t length){ can_frame_t frame; memset(&frame, 0, sizeof(can_frame_t)); /* 准备要发送的消息内容 */ memcpy(frame.data.byte, data, std::min(length, static_cast<size_t>(8))); frame.length = static_cast<uint8_t>(length); return kStatus_Success == CAN_TransferSendBlocking(CAN_PERIPHERAL_BASEADDR, &frame); } bool CCAN::Receive(uint8_t *buffer, size_t &length){ can_frame_t receivedFrame; status_t result = CAN_TransferReceiveBlocking(CAN_PERIPHERAL_BASEADDR, NULL, &receivedFrame, true); if(result != kStatus_Success || !receivedFrame.length){ return false; } /* 将接收到的内容复制给外部缓冲区 */ memcpy(buffer, receivedFrame.data.byte, std::min(static_cast<size_t>(receivedFrame.length), length)); length = receivedFrame.length; return true; } ``` 上述示例展示了基于C++封装的一个简化版CAN总线控制单元(CCAN)类,它能够满足大多数应用场景下对网络协议栈低层次支持的需求。当然,具体项目可能会因为需求差异而导致某些地方有所调整优化;不过总体框架应当保持一致以便更好地适应不同平台间的移植迁移工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值