确保电脑上已经安装最新的MPlab X IDE、XC32编译器和MCC harmony软件仓库,比如当前所使用的MPlab X IDE版本是V6.20
打开MPlab X IDE后,选择File->New Project:

新建工程
选择Application Project,点击下一步

选择Application Project
选择需要选择的器件型号,比如本教程选用的ATSAME54P20 MCU,点击下一步

选择器件型号
选择交叉编译工具,点击下一步

选择交叉编译工具
输入工程名称和选择存储路径,最后点击完成Finish

工程名称和存储路径
* 需要确保本地电脑上已经下载了Microchip MCC Harmony针对于所使用MCU型号的软件包,可以从github或者国内的gitee进行下载,并在MPlab X IDE里面进行配置。
* 配置过程可以通过Tools -> Option进入

MCC Harmony仓库配置参考
2. Microchip MCC Harmony配置界面说明
Microchip MCC harmony提供图形化的配置界面,能够方便的增减各种外设驱动库、中间件,提供图形化的系统时钟、管脚、DMA、事件系统和网络协议栈的配置,当完成配置后能够一键生成代码。

MCC Harmony配置界面
在官方SAME54 Xplained Pro开发板上完成以下配置目标
* 采用外部12MHz晶振输入
* 提供40M clock给CAN模块
* CAN工作在500kbps速率,采样点为75%
* 使用RX FIFO0来接收标准帧,FIFO1接收扩展帧
* 标准帧过滤器(Classic、Range模式)设置参考(只接收ID为0x1D0~0x1D7和0x1F0~0x1F8的帧)
* 扩展帧过滤器(Classic、Range模式)设置参考(只接收ID为0x1FFF1240~0x1FFF1248的帧)
3. 在MCC下配置系统的时钟
在Plugins里面打开Clock Configuration

打开Clock Configuration
打开时钟配置界面后,将看到以下的配置界面:

时钟配置界面
外部无源晶振输入的配置(外部12MHz晶振输入接在XOSC1上):

外部无源晶振输入的配置
XOSC CTRL 1配置的高级选项:

XOSC CTRL高级配置选项参考
MCU通用时钟发生器1~4的配置:

通用时钟发生器GCLK1的配置


MCU锁相环的配置:

FDPLL0的配置

外设时钟的配置:

外设时钟的配置
4. 在MCC下配置所需要使用的模块
配置调试打印模块
添加SERCOM2模块,选择MCC Resource Management -> Device Resources -> Libraries -> Harmony -> Peripherals -> SERCOM -> SERCOM2。

添加SERCOM2
配置SERCOM2模块,在Project Graph界面下左键单击新添加的SERCOM2模块

配置SERCOM2
一个SERCOM模块有4个PAD,PAD0~3,参考SAME54开发板原理图,打印用的串口PAD1为RX,PAD0为TX,所以配置SERCOM2的Receive/Transmit Pinout需要和原理图保持一致。

SERCOM2打印口管脚
添加STDIO模块,选择MCC Resource Management -> Device Resources -> Libraries -> Harmony -> Tools -> STDIO

添加STDIO
配置STDIO模块

配置STDIO
将STDIO和SERCOM2关联起来,右键点击STDIO组件下的粉色边框,选择Satisfiers下的SERCOM2即可。

将STDIO和SERCOM2绑定
STDIO标准打印接口绑定到SERCOM2,STDIO调试信息将输出到SERCOM2口

SERCOM2成功绑定到STDIO
配置CAN模块
添加CAN1模块,选择MCC Resource Management -> Device Resources -> Libraries -> Harmony -> Peripherals -> CAN -> CAN1。

添加CAN1
* 对添加的CAN1模块进行配置:
* 满足过滤规则的帧将存储在指定的FIFO中
* 提供40M clock给CAN模块
* CAN工作在500 kbps速率,采样点为75%
* 使用RX FIFO0来接收标准帧,FIFO1接收扩展帧
* 如果需要将CAN工作速率修改为250 kbps,只需Bit Rate手动输入250即可

CAN1模块配置1

* 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方式实现)

过滤器配置参考

CAN1模块其他配置项
配置管脚功能
配置STDIO打印用到的SERCOM口TX、RX管脚,CAN1模块用到的CAN1_TX、CAN1_RX和CAN收发器standby控制管脚PC13。

管脚分配图
在Project Graph界面下,打开Pin Configuration

打开管脚配置
随后在Pin Setting下对所需要的管脚进行配置。

管脚配置参考
修改系统堆栈大小
从Project Graph界面下,点击System模块,从右边配置树中修改Heap Size。

修改堆栈大小
生成代码

一键生成代码
5. 添加用户代码
在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;
}
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);
} 在CAN驱动初始化的时候需要调用GPIO PC13的clear操作,用于将CAN收发器ATA6561跳出standby模式。同时提供一个打印接收的CAN数据帧的函数,用来观察收到的CAN数据帧。打印函数的参数定义如下: * fifonum – CAN RX FIFO通道号 * numberofMessage——接收的消息数量 * rxBuf——接收消息的缓存区首地址 * rxBufLen——单条消息缓存区的长度
/* 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 );
}