Xilinx-ZYNQ7000系列-学习笔记(26):CAN总线
前言:一直忙着找工作和毕业的事,许久未操刀了,最近需要用到新的知识,宗旨还是在督促自己学习的同时,总结笔记,帮助大家。老本行不能丢!
一、CAN总线概述
1.1 CAN报文
在CAN总线上传输的信息称为报文,当CAN总线处于空闲状态时,任何连接的单元都可以发送新的报文。报文信号用查分电压传送,两条信号线称为CAN_H和CAN_L,静态时均为2.5V左右,表示逻辑1,叫做隐性。CAN_H比CAN_L高表示逻辑0,称为显性。
CAN报文有两种不同的帧格式:
标准格式(11位标识符)和扩展格式(可达29位标识符)。
其中标识符的作用是指明此帧数据发送的地址信息和数据信息的长度。
总线上的报文信息表示的帧类型:
(1)数据帧:从发送节点向其他节点发送的数据信息。
(2)远程帧:向其他节点请求发送具有同一标识符的数据帧。
(3)错误帧:检测到总线错误,发送的帧。
(4)过载帧:用在数据帧或远程帧之间提供附加的延时。
数据帧:由起始域、仲裁域、控制域、数据域、CRC域、应答域、和结尾域组成。
(1)起始域由一个单独的显性位组成。
(2)仲裁域由11位标识符和远程发送请求位RTR组成(扩展的是29位标识符和RTR)。
(3)控制域由6个位组成。
(4)数据域由发送数据组成。
(5)CRC域包括CRC序列和CRC分隔符。
(6)应答域长度位2个位,包含应答间隙和应答界定符。
(7)结尾域由7个隐性位组成。
远程帧:除了没有数据域外和RTR是隐性,剩下的与数据域相同。
错误帧:由不同站提供的错误标志的叠加和错误界定符两个场组成。
主动错误由6个连续的显性位组成,被动错误由6个连续的隐性位组成。
过载帧:包括过载标志和过载界定符。
1.2 报文滤波
在CAN总线上,CAN帧信息由一个节点发送,其他节点同时接收。每当总线上由帧信息时,节点都会把滤波器的设置和接收到的帧信息的标识码相比较,节点只接受符合一定条件的信息,对不符合条件的CAN帧不予接收而只给出应答信号。
1.3 CAN总线节点构成
TJA1050是独立的CAN收发器,通过MIO与FPGA相连。
1.4 CAN控制器的工作原理
CAN核心模块:收到一个报文时,该模块根据CAN规范将穿行位流转换成用于接收的并行数据,发送相反。
发送缓冲器:存储一个完整的报文。
验收滤波器:根据用户设定,过滤掉无须接收的报文。
接收FIFO:存储从CAN总线上接收的所有报文。
1.5 波特率的换算方法
1位分为4个段,每个段又由若干Tq构成,这称为位时序或时间份额。其中1位由多少个Tq构成、每个段由多少个Tq构成等,可以任意设定位时序。
同步段:用于同步总线上的不同节点(1Tq)。
传播时间段:吸收网络上的物理延迟的段。
相位缓冲段1:当信号边沿不能被包含于SS段中,可在此段进行补偿(1~8Tq)。
相位缓冲段2:也是补偿用的(2~8Tq)。
采样点是这样的时刻,总线电平被读取并被理解为其自身的数值。
在重同步期间,采样点的位置被移动整数个时间份额,该时间份额被允许的最大值称为重同步跳转宽度(SJW),可被编成为1~4个时间份额。
从数据手册中可以看出,CAN波特率是根据CAN的输入参考时钟频率得到的,这里举一个具体的例子便于理解:
CAN的输入参考时钟频率:freqCAN_REF_CLK = 24MHz
波特率分频值:can.BRPR[BRP] = 29
那么时间份额频率:freqTQ_CLK = 24MHz / (29+1) = 800KHz
设置同步跳转宽度为 3、相位缓冲段1为 2、相位缓冲段2位 15
CAN波特率:freqBIT_RATE = 24MHz / ((29+1)*(3+2+15) = 40kbps
二、CAN总线使用方法
2.1 CAN总线结构
下图为CAN控制器的框图:
配置寄存器(Configuration Registers):CAN 控制器配置寄存器定义了配置寄存器。该模块允许
用于通过APB 接口对寄存器进行读写访问。
收发消息(Transmit and Receive Messages):单独的存储缓冲区用于通过 FIFO 结构发送 (TxFIFO) 和接收 (RxFIFO) 消息。 每个缓冲区最多可存储 64 条消息。 将消息写入 TxFIFO 后,通过 CAN 总线传输需要 2*(Tx 驱动器延迟 + 传播延迟 + Rx 驱动器延迟)的总延迟。
Tx高级优先缓冲区(Tx High Priority Buffer):每个控制器还有一个传输高优先级缓冲区 (TxHPB),为一个传输消息提供存储空间。 写入此缓冲区的消息具有最大传输优先级。 它们在当前传输完成后立即排队等待传输,抢占 TxFIFO 中的任何消息。
验收过滤器(Acceptance Filters):接受过滤器使用用户定义的接受掩码和 ID 寄存器对传入的消息进行排序,以确定是将消息存储在 RxFIFO 中,还是确认并丢弃它们。 通过验收过滤器的消息存储在 RxFIFO 中。
2.2 CAN控制器的工作模式
(1)配置模式(Configuration Mode):此模式下进行相应的配置。
(2)正常模式(Normal Mode):正常模式根据 Bosch 和 IEEE 规范定义的 Tx 和 Rx I/O 信号发送和接收消息。
(3)睡眠模式(Sleep Mode):休眠模式可用于在空闲时间节省少量电量。 当处于睡眠模式时,控制器可以转换到正常模式或配置模式。在睡眠模式下:a.当另一个节点有数据发送来,控制器接收消息并退出睡眠模式。b.如果有新的Tx请求,硬件控制器会切换到正常模式。c.在睡眠模式下的控制器可以产生中断。d.控制器唤醒时可以产生中断。
(4)回环模式(Loop Back Mode):回环模式用于诊断。控制器不接收其他 CAN 节点发送的任何消息。
(5)监听模式(Snoop Mode):监听模式用于诊断。控制器接收由其他 CAN 节点传输的消息。
2.3 代码解析
int CanPsPolledExample(u16 DeviceId)
{
int Status;
XCanPs *CanInstPtr = &Can;
XCanPs_Config *ConfigPtr;
//(1)初始化查找表
ConfigPtr = XCanPs_LookupConfig(DeviceId);
if (CanInstPtr == NULL) {
return XST_FAILURE;
}
//(2)初始化CAN设备
Status = XCanPs_CfgInitialize(CanInstPtr,
ConfigPtr,
ConfigPtr->BaseAddr);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
//(3)CAN自检
Status = XCanPs_SelfTest(CanInstPtr);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
//(4)CAN控制器各种设置
XCanPs_EnterMode(CanInstPtr, XCANPS_MODE_CONFIG);
while(XCanPs_GetMode(CanInstPtr) != XCANPS_MODE_CONFIG);
XCanPs_SetBaudRatePrescaler(CanInstPtr, TEST_BRPR_BAUD_PRESCALAR);
XCanPs_SetBitTiming(CanInstPtr, TEST_BTR_SYNCJUMPWIDTH,
TEST_BTR_SECOND_TIMESEGMENT,
TEST_BTR_FIRST_TIMESEGMENT);
XCanPs_EnterMode(CanInstPtr, XCANPS_MODE_LOOPBACK);
while(XCanPs_GetMode(CanInstPtr) != XCANPS_MODE_LOOPBACK);
Status = SendFrame(CanInstPtr);
if (Status != XST_SUCCESS) {
return Status;
}
Status = RecvFrame(CanInstPtr);
return Status;
}
(1)设置查找表
XCanPs_Config *XCanPs_LookupConfig(u16 DeviceId)
{
XCanPs_Config *CfgPtr = NULL;
u32 Index;
for (Index = 0U; Index < (u32)XPAR_XCANPS_NUM_INSTANCES; Index++) {
if (XCanPs_ConfigTable[Index].DeviceId == DeviceId) {
CfgPtr = &XCanPs_ConfigTable[Index];
break;
}
}
return (XCanPs_Config *)CfgPtr;
}
用来区分CAN0、CAN1,并将相应的CAN设备号和基地址信息保存到结构体数据中。
(2)初始化CAN设备
s32 XCanPs_CfgInitialize(XCanPs *InstancePtr, XCanPs_Config *ConfigPtr,
u32 EffectiveAddr)
{
s32 Status;
Xil_AssertNonvoid(InstancePtr != NULL);
Xil_AssertNonvoid(ConfigPtr != NULL);
/*
* Set some default values for instance data, don't indicate the device
* is ready to use until everything has been initialized successfully.
*/
InstancePtr->IsReady = 0U;
InstancePtr->CanConfig.BaseAddr = EffectiveAddr;
InstancePtr->CanConfig.DeviceId = ConfigPtr->DeviceId;
/*
* Set all handlers to stub values, let user configure this data later.
*/
InstancePtr->SendHandler = (XCanPs_SendRecvHandler) StubHandler;
InstancePtr->RecvHandler = (XCanPs_SendRecvHandler) StubHandler;
InstancePtr->ErrorHandler = (XCanPs_ErrorHandler) StubHandler;
InstancePtr->EventHandler = (XCanPs_EventHandler) StubHandler;
/*
* Indicate the component is now ready to use.
*/
InstancePtr->IsReady = XIL_COMPONENT_IS_READY;
/*
* Reset the device to get it into its initial state.
*/
XCanPs_Reset(InstancePtr);
Status = XST_SUCCESS;
return Status;
}
将CAN0的信息保存到一个CAN的结构体实例中,其中包括所有的CAN信息
然后进行CAN复位,将CAN控制器复位:
void XCanPs_Reset(XCanPs *InstancePtr)
{
Xil_AssertVoid(InstancePtr != NULL);
Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);
XCanPs_WriteReg(InstancePtr->CanConfig.BaseAddr, XCANPS_SRR_OFFSET, \
XCANPS_SRR_SRST_MASK);
}
(3)CAN自检
s32 XCanPs_SelfTest(XCanPs *InstancePtr)
{
u8 *FramePtr;
s32 Status;
u32 Index;
u8 GetModeResult;
u32 RxEmptyResult;
Xil_AssertNonvoid(InstancePtr != NULL);
Xil_AssertNonvoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);
XCanPs_Reset(InstancePtr);
if (XCanPs_GetMode(InstancePtr) != XCANPS_MODE_CONFIG) {
Status = XST_FAILURE;
return Status;
}
(void)XCanPs_SetBaudRatePrescaler(InstancePtr, (u8)29U);
(void)XCanPs_SetBitTiming(InstancePtr, (u8)3U, (u8)2U, (u8)15U);
XCanPs_EnterMode(InstancePtr, XCANPS_MODE_LOOPBACK);
GetModeResult = XCanPs_GetMode(InstancePtr);
while (GetModeResult != ((u8)XCANPS_MODE_LOOPBACK)) {
GetModeResult = XCanPs_GetMode(InstancePtr);
}
TxFrame[0] = (u32)XCanPs_CreateIdValue((u32)2000U, (u32)0U, (u32)0U, (u32)0U, (u32)0U);
TxFrame[1] = (u32)XCanPs_CreateDlcValue((u32)8U);
FramePtr = (u8 *)((void *)(&TxFrame[2]));
for (Index = 0U; Index < 8U; Index++) {
if(*FramePtr != 0U) {
*FramePtr = (u8)Index;
FramePtr++;
}
}
Status = XCanPs_Send(InstancePtr, TxFrame);
if (Status != (s32)XST_SUCCESS) {
Status = XST_FAILURE;
return Status;
}
RxEmptyResult = XCanPs_ReadReg(((InstancePtr)->CanConfig.BaseAddr),
XCANPS_ISR_OFFSET) & XCANPS_IXR_RXNEMP_MASK;
while (RxEmptyResult == (u32)0U) {
RxEmptyResult = XCanPs_ReadReg(((InstancePtr)->CanConfig.BaseAddr),
XCANPS_ISR_OFFSET) & XCANPS_IXR_RXNEMP_MASK;
}
Status = XCanPs_Recv(InstancePtr, RxFrame);
if (Status != (s32)XST_SUCCESS) {
Status = XST_FAILURE;
return Status;
}
if (RxFrame[0] !=
(u32)XCanPs_CreateIdValue((u32)2000U, (u32)0U, (u32)0U, (u32)0U, (u32)0U)) {
Status = XST_FAILURE;
return Status;
}
if ((RxFrame[1] & ~XCANPS_DLCR_TIMESTAMP_MASK) != TxFrame[1]) {
Status = XST_FAILURE;
return Status;
}
for (Index = 2U; Index < (XCANPS_MAX_FRAME_SIZE_IN_WORDS); Index++) {
if (RxFrame[Index] != TxFrame[Index]) {
Status = XST_FAILURE;
return Status;
}
}
XCanPs_Reset(InstancePtr);
Status = XST_SUCCESS;
return Status;
}
首先读取当前CAN控制器的工作模式,通过XCanPs_GetMode来完成,通常情况下复位后是配置模式。查找模式通过读取寄存器XCANPS_SR_OFFSET的位来读取进行判断。
然后设置波特率预分频值和位时序,进行这两个操作要确保在配置模式下。
小琛查了一下:对于bit time的解释可以看看这篇写的。
然后配置CAN控制器的工作模式为LOOPBACK模式,并通过while循环一直等待其配置好。
接下来开始构建消息数据,每条消息包括4个字(16字节),CAN的消息格式如下:
对消息格式中的内容进行简介:
ID(Standard Message ID):标识符部分为11位。
STR/RTR(Substitute Remote Transmission Request):用来区分数据帧(0)还是远程帧(1)。
IDE(Identifier Extension):标识符扩展位,用来区分标准帧(0)还是扩展帧(1)。
ID[17:0](Extended Message ID):用于扩展的。
DLC(Data Length Code):帧数据字节长度。
DB(Data Length Code):数据字节。
构建完消息后通过XCanPs_Send函数将消息发送出去。
通过检查RX FIFO是否为空来判断消息是否发送成功。
如果发送成功,则通过XCanPs_Recv函数将消息接收回来。
将接收回来的消息与发送出去的消息进行对比进而判断收发过程是否正常。
如果正常,复位CAN控制器,完成自检过程。
(4)CAN控制器各种设置
XCanPs_EnterMode(CanInstPtr, XCANPS_MODE_CONFIG);
while(XCanPs_GetMode(CanInstPtr) != XCANPS_MODE_CONFIG);
XCanPs_SetBaudRatePrescaler(CanInstPtr, TEST_BRPR_BAUD_PRESCALAR);
XCanPs_SetBitTiming(CanInstPtr, TEST_BTR_SYNCJUMPWIDTH,
TEST_BTR_SECOND_TIMESEGMENT,
TEST_BTR_FIRST_TIMESEGMENT);
XCanPs_EnterMode(CanInstPtr, XCANPS_MODE_LOOPBACK);
while(XCanPs_GetMode(CanInstPtr) != XCANPS_MODE_LOOPBACK);
首先设置模式为配置模式,然后对波特率预分频值和位时序进行配置。设置的值与自检时的相同。
最后将模式设置为LOOP BACK模式。
(5)发送消息
static int SendFrame(XCanPs *InstancePtr)
{
u8 *FramePtr;
int Index;
int Status;
TxFrame[0] = (u32)XCanPs_CreateIdValue((u32)TEST_MESSAGE_ID, 0, 0, 0, 0);
TxFrame[1] = (u32)XCanPs_CreateDlcValue((u32)FRAME_DATA_LENGTH);
FramePtr = (u8 *)(&TxFrame[2]);
for (Index = 0; Index < FRAME_DATA_LENGTH; Index++) {
*FramePtr++ = (u8)Index;
}
while (XCanPs_IsTxFifoFull(InstancePtr) == TRUE);
Status = XCanPs_Send(InstancePtr, TxFrame);
return Status;
}
构建结果如下:
#define XCanPs_CreateIdValue(StandardId, SubRemoteTransReq, IdExtension, \
ExtendedId, RemoteTransReq) \
((((StandardId) << XCANPS_IDR_ID1_SHIFT) & XCANPS_IDR_ID1_MASK) | \
(((SubRemoteTransReq) << XCANPS_IDR_SRR_SHIFT) & XCANPS_IDR_SRR_MASK)|\
(((IdExtension) << XCANPS_IDR_IDE_SHIFT) & XCANPS_IDR_IDE_MASK) | \
(((ExtendedId) << XCANPS_IDR_ID2_SHIFT) & XCANPS_IDR_ID2_MASK) | \
((RemoteTransReq) & XCANPS_IDR_RTR_MASK))
解析如下:
(((((u32)2000) << 21U) & 0xFFE00000U) | \
(((0) << 20U) & 0x00100000U)|\
(((0) << 19U) & 0x00080000U) | \
(((0) << 1U) & 0x0007FFFEU) | \
((0) & 0x00000001U))
#define XCanPs_CreateDlcValue(DataLengCode) \
(((DataLengCode) << XCANPS_DLCR_DLC_SHIFT) & XCANPS_DLCR_DLC_MASK)
解析如下:
((((u32)8) << 28U) & 0xF0000000U)
发送消息包括构建消息和发送消息,构建标准帧的内容上文已讲,构建标准帧其实就是将各种信息写到相应的寄存器中:
s32 XCanPs_Send(XCanPs *InstancePtr, u32 *FramePtr)
{
s32 Status;
Xil_AssertNonvoid(InstancePtr != NULL);
Xil_AssertNonvoid(FramePtr != NULL);
Xil_AssertNonvoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);
if (XCanPs_IsTxFifoFull(InstancePtr) == TRUE) {
Status = XST_FIFO_NO_ROOM;
} else {
XCanPs_WriteReg(InstancePtr->CanConfig.BaseAddr,
XCANPS_TXFIFO_ID_OFFSET, FramePtr[0]);
XCanPs_WriteReg(InstancePtr->CanConfig.BaseAddr,
XCANPS_TXFIFO_DLC_OFFSET, FramePtr[1]);
XCanPs_WriteReg(InstancePtr->CanConfig.BaseAddr,
XCANPS_TXFIFO_DW1_OFFSET, Xil_EndianSwap32(FramePtr[2]));
XCanPs_WriteReg(InstancePtr->CanConfig.BaseAddr,
XCANPS_TXFIFO_DW2_OFFSET, Xil_EndianSwap32(FramePtr[3]));
Status = XST_SUCCESS;
}
return Status;
}
(6)接收消息
static int RecvFrame(XCanPs *InstancePtr)
{
u8 *FramePtr;
int Status;
int Index;
while (XCanPs_IsRxEmpty(InstancePtr) == TRUE);
Status = XCanPs_Recv(InstancePtr, RxFrame);
if (Status == XST_SUCCESS) {
if (RxFrame[0] !=
(u32)XCanPs_CreateIdValue((u32)TEST_MESSAGE_ID, 0, 0, 0, 0))
return XST_LOOPBACK_ERROR;
if ((RxFrame[1] & ~XCANPS_DLCR_TIMESTAMP_MASK) != TxFrame[1])
return XST_LOOPBACK_ERROR;
FramePtr = (u8 *)(&RxFrame[2]);
for (Index = 0; Index < FRAME_DATA_LENGTH; Index++) {
if (*FramePtr++ != (u8)Index) {
return XST_LOOPBACK_ERROR;
}
}
}
return Status;
}
接收消息则是将接收到的帧数据与之前设定的相对比,这里不再赘述。