PIC32MZ0512EFF相关的CAN配置(学习记录)
鉴于PIC相关资料较少,我在这记录分享一下自己经过一段时间的学习试验以后对PIC32系列的CAN的理解。记录不仅仅是分享,同样也是整理归纳查漏补缺的过程。在这个过程中大家互相学习,共同进步。
在使用芯片PIC32MZ0512EFF配置CAN初始化的过程中,我遇到了一个问题:这个芯片的CAN部分没有直接供我们使用的发送寄存器和接收寄存器。
下面会描述一下我的解决方法,并说说我的理解。
直接上干货:这里把对CAN2的初始化的全部过程都写出来。每个步骤在后文都会有详细的代码示例。
void CAN2_Init(void)
{
CAN2_GPIOPPSConfig();
//第一步:对CAN2进行引脚分配(PPS映射),以及设置相应的输入输出
C2CONSET = _C2CON_ON_MASK;
//第二步:启用can2模块,也可以写成C2CONbits.ON = 1;
CAN2_ConfigurationMode();
//第三步:顾名思义,就是进入CAN2的配置模式
CAN2_BaudrateInit();
//第四步:配置CAN2的波特率
C2FIFOBA = 0x0000100;
//第五步:设置FIFO起始地址
CAN2_FIFOn();
//第六步:设置FIFO,包括需要用到的FIFO数量以及每个FIFO相应的功能和容量大小
CAN2_FilterMaskInit();
//第七步:设置过滤器和屏蔽器
CAN2_LoopMode();
//第八步:进入CAN2的自检模式(环回模式),当然如果相应引脚与CAN收发器连接了,可以进入正常模式
}
注:后面按顺序分4个部分展示第一步,第三步和第八步,第四步,第五六七步的代码和我的理解
#第一部分
void CAN2_GPIOPPSConfig(void)
{
SYSKEY = 0x00000000;
SYSKEY = 0xAA996655;
SYSKEY = 0x556699AA;
CFGCONbits.IOLOCK = 0;
//解锁序列
//在芯片手册里有说到:正常工作状态下,对PPS管脚映射功能配置失效。要想使用,需要执行一个解锁序列,然后把IOLOCK清零
C2RXR = 4;
//把RD4设为C2RX
RPD5R = 15;
//把RD5设为C2TX
CFGCONbits.IOLOCK = 1;
SYSKEY = 0x00000000;
//设完PPS后把管脚映射锁置1,把序列清零
TRISDSET = _PORTD_RD4_MASK;
//可以写成TRISDbits.TRISD4 = 1;
TRISDCLR = _PORTD_RD5_MASK;
//可以写成TRISDbits.TRISD5 = 0;
}
注:对解锁序列的理解不深,这里就不发表看法了。但还是说一下我实际操作中的现象:
1、我在没有执行解锁序列的情况下也能使用管脚映射功能(这应该和我的配置位设置内容有关联)
2、如果之后要设置为环回模式,请确保所要使用的引脚悬空
#第二部分
void CAN2_ConfigurationMode(void)
{
C2CONbits.REQOP = 4;//配置为配置模式
while(C2CONbits.OPMOD!=4);//等待配置模式配置完毕
}
void CAN2_LoopMode(void)
{
C2CONbits.REQOP = 2;//配置为环回模式
while(C2CONbits.OPMOD!=2);//等待环回模式配置完毕
}
void CAN2_NormalMode(void)
{
C2CONbits.REQOP = 0;//配置为正常模式
while(C2CONbits.OPMOD!=0);//等待正常模式配置完毕
}
注:环回模式可以在不接外部芯片(比如CAN收发芯片MCP2551)的情况下测试程序
#第三部分
void CAN2_BaudrateInit(void)
{
//500Mbps
C2CFGbits.BRP = 9;//波特率预分频
C2CFGbits.SJW = 0;//同步跳转宽度
C2CFGbits.PRSEG = 0;//传播时间段(Prop_Seg)
C2CFGbits.SEG1PH = 3;//相位缓冲段1(Phase_Seg1)
C2CFGbits.SAM = 0;//在采样点采样一次
C2CFGbits.SEG2PHTS = 1;//表示相位缓冲段2可自由编程
C2CFGbits.SEG2PH = 3;//相位缓冲段2(Phase_Seg2)
}
/*波特率计算公式:
* PBCLK5
* baud = ----------------------------------------------
* (Prop_Seg+Phase_Seg1+Phase_Seg2+SJW)*2*(BRP+1)
*/
//我用的外部晶振,在进行倍频处理后为200MHz,PBCLK5为CAN的外设时钟,外设时钟默认分频2,所以PBCLK5 = 100MHz
#第四部分
void CAN2_FIFOn(void)
{
C2FIFOCON0bits.FSIZE = 31;//FIFO0深度为32个缓冲区
C2FIFOCON0bits.TXEN = 1;//FIFO0配置为发送区
C2FIFOCON1bits.FSIZE = 0;//FIFO1深度为1个缓冲区
C2FIFOCON1bits.DONLY = 0;
C2FIFOCON1bits.TXEN = 0;//FIFO1配置为接收区
C2FIFOCON2bits.FSIZE = 0;//FIFO2深度为1个缓冲区
C2FIFOCON2bits.DONLY = 0;
C2FIFOCON2bits.TXEN = 0;//FIFO2配置为接收区
C2FIFOCON3bits.FSIZE = 0;//FIFO3深度为1个缓冲区
C2FIFOCON3bits.DONLY = 0;
C2FIFOCON3bits.TXEN = 0;//FIFO3配置为接收区
C2FIFOCON4bits.FSIZE = 0;//FIFO4深度为1个缓冲区
C2FIFOCON4bits.DONLY = 0;
C2FIFOCON4bits.TXEN = 0;//FIFO4配置为接收区
}
//这里解释一下FIFO的机制:就我所使用的芯片,拥有2个CAN区域,每个CAN拥有32个FIFO,每个FIFO拥有1-32个可配置的数据缓冲区,每个数据缓冲区
//可容纳4*32个bit或是1个报文(包括ID和数据)。每个FIFO都可以配置为发送区或是接收区,且FIFO紧密排列。
//例如上面的程序:CAN2的FIFO排列如下
//FIFO0 发送区 可以容纳32个报文 物理地址0X00000100
//FIFO1 接收区 可以容纳1 个报文 物理地址0X00000300
//FIFO2 接收区 可以容纳1 个报文 物理地址0X00000310
//FIFO3 接收区 可以容纳1 个报文 物理地址0X00000320
//FIFO4 接收区 可以容纳1 个报文 物理地址0X00000330
//FIFO5 未分配 可以容纳1 个报文 物理地址0X00000340
//FIFO6 未分配 可以容纳1 个报文 物理地址0X00000350
//........
//FIFO31 未分配 可以容纳1 个报文 物理地址0X000004F0
void CAN2_FilterMaskInit(void)
{
C2FLTCON0bits.FSEL0 = 1;//接收到的与过滤器0匹配的数据存储在FIFO1中
C2FLTCON0bits.MSEL0 = 1;//选择接收屏蔽器1
C2RXF0bits.EID = 0x0;//设置过滤器0
C2RXF0bits.SID = 0xE;
C2RXF0bits.EXID = 1;
C2FLTCON0bits.FSEL1 = 2;//接收到的与过滤器1匹配的数据存储在FIFO2中
C2FLTCON0bits.MSEL1 = 1;//选择接收屏蔽器1
C2RXF1bits.EID = 0x0;//设置过滤器1
C2RXF1bits.SID = 0xA;
C2RXF1bits.EXID = 1;
C2FLTCON0bits.FSEL2 = 3;//接收到的与过滤器2匹配的数据存储在FIFO3中
C2FLTCON0bits.MSEL2 = 1;//选择接收屏蔽器1
C2RXF2bits.EID = 0x0;//设置过滤器2
C2RXF2bits.SID = 0xC;
C2RXF2bits.EXID = 1;
C2FLTCON0bits.FSEL3 = 4;//接收到的与过滤器3匹配的数据存储在FIFO4中
C2FLTCON0bits.MSEL3 = 1;//选择接收屏蔽器1
C2RXF3bits.EID = 0x0;//设置过滤器3
C2RXF3bits.SID = 0x8;
C2RXF3bits.EXID = 1;
C2RXM1bits.EID = 0x0;//设置屏蔽器1
C2RXM1bits.SID = 0xF;
C2RXM1bits.MIDE = 1;
C2FLTCON0bits.FLTEN0 = 1;//启用过滤器
C2FLTCON0bits.FLTEN1 = 1;
C2FLTCON0bits.FLTEN2 = 1;
C2FLTCON0bits.FLTEN3 = 1;
}
以下为接收发送时需要用到的宏,这几个宏在XC32编译器手册中被提到
typedef unsigned long _paddr_t; //物理地址
typedef unsigned long _vaddr_t; //虚拟地址
#define KVA_TO_PA(v) ((_paddr_t)(v) & 0x1fffffff)//将内核地址转换成物理地址
#define PA_TO_KVA0(pa) ((void *) ((pa) | 0x80000000))//将物理地址转换成KSEG0虚拟地址
#define PA_TO_KVA1(pa) ((void *) ((pa) | 0xa0000000))//将物理地址转换成KSEG1虚拟地址
先上图:
在PIC32中CPU所使用的是左边的虚拟地址,而外设使用的是右边的物理地址。也即使说软件操作写入外设寄存器(左边),CPU会对其转换为右边的物理地址。手册中指出C2FIFOBA、C2FIFOUAn为物理地址。C2FIFOUAn为FIFOn的起始地址,在需要从FIFO中读取或写入数据时,需要将C2FIFOUAn转换为左边的虚拟地址后通过软件读取或写入。
typedef union
{
struct
{
unsigned TXCMSGSID_SID:11;
unsigned :21;
unsigned TXCMSGEID_DLC:4;
unsigned TXCMSGEID_RB0:1;
unsigned :3;
unsigned TXCMSGEID_RB1:1;
unsigned TXCMSGEID_RTR:1;
unsigned TXCMSGEID_EID:18;
unsigned TXCMSGEID_IDE:1;
unsigned TXCMSGEID_SRR:1;
unsigned :2;
unsigned TXCMSGDATA0_Byte0:8;
unsigned TXCMSGDATA0_Byte1:8;
unsigned TXCMSGDATA0_Byte2:8;
unsigned TXCMSGDATA0_Byte3:8;
unsigned TXCMSGDATA1_Byte4:8;
unsigned TXCMSGDATA1_Byte5:8;
unsigned TXCMSGDATA1_Byte6:8;
unsigned TXCMSGDATA1_Byte7:8;
};
uint32_t TXMessage[4];
}CANTXBuffer;
typedef union
{
struct
{
unsigned RXCMSGSID_SID:11;
unsigned RXCMSGSID_FILHIT:5;
unsigned RXCMSGSID_CMSGTS:16;
unsigned RXCMSGEID_DLC:4;
unsigned RXCMSGEID_RB0:1;
unsigned :3;
unsigned RXCMSGEID_RB1:1;
unsigned RXCMSGEID_RTR:1;
unsigned RXCMSGEID_EID:18;
unsigned RXCMSGEID_IDE:1;
unsigned RXCMSGEID_SRR:1;
unsigned :2;
unsigned RXCMSGDATA0_Byte0:8;
unsigned RXCMSGDATA0_Byte1:8;
unsigned RXCMSGDATA0_Byte2:8;
unsigned RXCMSGDATA0_Byte3:8;
unsigned RXCMSGDATA1_Byte4:8;
unsigned RXCMSGDATA1_Byte5:8;
unsigned RXCMSGDATA1_Byte6:8;
unsigned RXCMSGDATA1_Byte7:8;
};
uint32_t RXMessage[4];
}CANRXBuffer;
CANTXBuffer *CAN2TXBuffer;
CAN2TXBuffer = (CANTXBuffer*)(PA_TO_KVA1(C2FIFOUA0));
//相应内容存储于以下
//CAN2TXBuffer->TXMessage[0]
//CAN2TXBuffer->TXMessage[1]
//CAN2TXBuffer->TXMessage[2]
//CAN2TXBuffer->TXMessage[3]
CANRXBuffer *CAN2RXBuffer1,*CAN2RXBuffer2,*CAN2RXBuffer3,*CAN2RXBuffer4;
CAN2RXBuffer1 = (CANRXBuffer*)(PA_TO_KVA1(C2FIFOUA1));
//相应内容存储于以下
//CAN2RXBuffer1->RXMessage[0]
//CAN2RXBuffer1->RXMessage[1]
//CAN2RXBuffer1->RXMessage[2]
//CAN2RXBuffer1->RXMessage[3]
CAN2RXBuffer2 = (CANRXBuffer*)(PA_TO_KVA1(C2FIFOUA2));
//相应内容存储于以下
//CAN2RXBuffer2->RXMessage[0]
//CAN2RXBuffer2->RXMessage[1]
//CAN2RXBuffer2->RXMessage[2]
//CAN2RXBuffer2->RXMessage[3]
CAN2RXBuffer3 = (CANRXBuffer*)(PA_TO_KVA1(C2FIFOUA3));
//相应内容存储于以下
//CAN2RXBuffer3->RXMessage[0]
//CAN2RXBuffer3->RXMessage[1]
//CAN2RXBuffer3->RXMessage[2]
//CAN2RXBuffer3->RXMessage[3]
CAN2RXBuffer4 = (CANRXBuffer*)(PA_TO_KVA1(C2FIFOUA4));
//相应内容存储于以下
//CAN2RXBuffer4->RXMessage[0]
//CAN2RXBuffer4->RXMessage[1]
//CAN2RXBuffer4->RXMessage[2]
//CAN2RXBuffer4->RXMessage[3]