Zynq平台使用CAN通信开发实战指南
前言
在Zynq SoC平台上进行CAN通信开发,具有高性能、低延迟等优势,适合用于工业控制、汽车电子等对实时性要求较高的场景。
本文将围绕Zynq-7010平台,介绍在裸机环境和FreeRTOS中使用CAN通信的完整开发流程,实现250Kbps的CAN扩展帧通信,包括工程配置、驱动开发、中断管理、波特率设置以及调试经验等内容。
本文适用于对 Zynq 开发体系具有一定基础的读者,且不对基础的知识过于展开描述。
1. 基础介绍
1.1 平台介绍
Zynq-7000 系列 SoC 是 Xilinx 推出的融合了 ARM Cortex-A9 核心与 FPGA 可编程逻辑的片上系统(SoC),广泛应用于工业、汽车、通信等领域。其架构将处理系统(PS)与可编程逻辑(PL)通过高速 AXI 接口紧密耦合,使得在嵌入式系统中兼具灵活性与高性能。
本项目基于 Zynq-7010 芯片开发,使用开发工具如下:
- 硬件平台:Zynq-7010
- 开发工具链:
- Vivado 2017.4
- XSDK
- 调试工具:JTAG + USB-CAN模块 + 上位机CAN调试软件
Zynq PS端集成了两个独立的 CAN 控制器(CAN0 和 CAN1),均支持标准帧和扩展帧格式,且支持中断和轮询两种通信模式。
1.2 CAN协议基础
CAN(Controller Area Network)是一种多主机通信协议,最早由 Bosch 公司为汽车电子开发,具有高可靠性、实时性和抗干扰能力强的优点。以下是 CAN 协议的几个关键点:
-
帧格式:
- 标准帧(11位标识符)
- 扩展帧(29位标识符)
- 支持数据帧、远程帧、错误帧和过载帧
-
通信方式:
- 多主广播机制,采用非破坏性仲裁方式(ID越小优先级越高)
- 单线接收双线差分发送(CAN_H 与 CAN_L)
-
物理层要求:
- 总线两端应各连接一个120Ω终端电阻
- 推荐布线总长度不超过40米,速率受线长影响显著
-
常用波特率:
- 10Kbps、50Kbps、125Kbps、250Kbps、500Kbps、1Mbps
CAN因其开放性和稳定性,被广泛应用于工业控制、楼宇自动化、医疗仪器及车辆系统。
1.3 Zynq上使用CAN的注意事项
Zynq使用CAN模块时,需要注意以下几点:
-
CAN控制器位置:Zynq的CAN控制器集成在PS端,不在PL中使用逻辑实现。
-
时钟配置:CAN控制器的时钟源需从 Vivado配置,官方例程推荐使用 24MHz作为CAN时钟,具体根据需求修改
1.4 CAN引脚配置与硬件连接
在 Vivado 工程中,CAN 的引脚配置通常有两种方式:
-
MIO引脚:
- 可直接在 ZYNQ7 IP 中使能 MIO,例如:
- MIO10:CAN0_TX
- MIO11:CAN0_RX
- 引脚受限于板载资源,需查阅原理图确认是否可用
- 可直接在 ZYNQ7 IP 中使能 MIO,例如:
-
EMIO引脚(推荐用于 PL 引脚输出):
- 可通过 EMIO 将 CAN 信号输出到 PL,再通过
IO Buffer
引脚约束输出至收发器 - 适用于 MIO 不足或 MIO已被占用的情况
- 可通过 EMIO 将 CAN 信号输出到 PL,再通过
-
注意事项:
- 收发器建议独立供电(推荐3.3V或5V)
- 与其他设备通信时,需确认双方波特率一致
2. 具体上手
2.1 工程配置
- ** Vivado 中的硬件平台设计**
-
Blockdesign
-
使能CAN,共有两个CAN,根据自己需求使能
-
配置时钟。关于时钟,官方例程24M,我个人调整为40M
-
管脚XDC约束,根据个人需求,我这边使用EMIO
-
- XSDK
- 这里不介绍工程建立,可参照官方源码修改
- 这里不介绍工程建立,可参照官方源码修改
2.2 CAN驱动源码
以下是我个人根据官方例程修改的驱动程序,驱动支持 标准帧/扩展帧的收发、完整中断处理、回调注册机制,结构说明在后面
- 源码
/*
* dev_can_ps_0.c
*
* Created on: 2024年5月11日
* Author: Jafi666
*/
#include "xparameters.h"
#include "xcanps.h"
#include "xil_exception.h"
#include "xil_printf.h"
#include "stdio.h"
#include "xscugic.h"
#include "dev_can_ps_0.h"
#define CAN_DEVICE_ID XPAR_XCANPS_0_DEVICE_ID
#define CAN_INTR_VEC_ID XPAR_XCANPS_0_INTR
/* Maximum CAN frame length in word */
#define XCANPS_MAX_FRAME_SIZE_IN_WORDS (XCANPS_MAX_FRAME_SIZE / sizeof(u32))
/*
* 设置can工作模式
* */
//#define CAN_MODE XCANPS_MODE_LOOPBACK /*内部回环模式,可在这个模式下先验证软件工程*/
#define CAN_MODE XCANPS_MODE_NORMAL /*普通工作模式*/
/*
* 数据限制
*/
#define MIN_DATA_LENGTH 1 /* Frame Data field length */
#define MAX_DATA_LENGTH 8 /* Frame Data field length */
/*
* id上限
*/
#define MAX_StandardId 0x7FF/*标准帧id上限*/
#define MAX_ExtendedId 0x1FFFFFFF/*扩展帧id上限*/
#define BTR_SYNCJUMPWIDTH 0 //同步跳转宽度
#define BTR_SECOND_TIMESEGMENT 1 //TSEG2
#define BTR_FIRST_TIMESEGMENT 12 //TSEG1
#define BRPR_BAUD_PRESCALAR 9
/*40MHz时钟频率,波特率250k可用
#define BTR_SYNCJUMPWIDTH 0//同步跳转宽度
#define BTR_SECOND_TIMESEGMENT 1 //TSEG2
#define BTR_FIRST_TIMESEGMENT 12 //TSEG1
#define BRPR_BAUD_PRESCALAR 9
*/
/*100MHz时钟频率,波特率1M可用
#define BTR_SYNCJUMPWIDTH 3
#define BTR_SECOND_TIMESEGMENT 2
#define BTR_FIRST_TIMESEGMENT 15
#define BRPR_BAUD_PRESCALAR 4
*/
static void Can_Ps_Config(XCanPs *InstancePtr);
static void SendHandler(void *CallBackRef);
static void RecvHandler(void *CallBackRef);
static void ErrorHandler(void *CallBackRef, u32 ErrorMask);
static void EventHandler(void *CallBackRef, u32 Mask);
static void Ps_CAN_0_Recv( unsigned long MESSAGE_ID,
unsigned long DATA_LENGTH,
unsigned char *RECV_DATA);
/************************** Variable Definitions *****************************/
static XCanPs CanInstance; /* Instance of the Can driver */
/*
* Buffers to hold frames to send and receive. These are declared as global so
* that they are not on the stack.
* These buffers need to be 32-bit aligned
*/
static u32 TxFrame[XCANPS_MAX_FRAME_SIZE_IN_WORDS];
static u32 RxFrame[XCANPS_MAX_FRAME_SIZE_IN_WORDS];
volatile static int LoopbackError; /* Asynchronous error occurred */
volatile static int RecvDone; /* Received a frame */
volatile static int SendDone; /* Frame was sent successfully */
static CAN0_Recv_Callback can_recv_callback = NULL;//全局的回调函数指针:
/**********************************************************************
* 函数名称: CAN0_Register_Recv_Callback
* 功能描述: 注册回调函数
* 输入参数:
* 输出参数:
* 返 回 值:
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2024/05/26 V1.0 DEMO 创建
***********************************************************************/
void CAN0_Register_Recv_Callback(CAN0_Recv_Callback callback) {
can_recv_callback = callback;
}
/**********************************************************************
* 函数名称: Can_Ps_Config
* 功能描述: 配置ps can控制器
* 输入参数: null
* 输出参数: 无
* 返 回 值: Status
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2024/05/11 V1.0 DEMO 创建
***********************************************************************/
static void Can_Ps_Config(XCanPs *InstancePtr)
{
/*
* Enter Configuration Mode if the device is not currently in
* Configuration Mode.
*/
XCanPs_EnterMode(InstancePtr, XCANPS_MODE_CONFIG);//进入配置模式,配置模式允许用户设置控制器的参数,如波特率和位定时,而不干扰正在进行的通信
while(XCanPs_GetMode(InstancePtr) != XCANPS_MODE_CONFIG);
/*
* Setup Baud Rate Prescaler Register (BRPR) and
* Bit Timing Register (BTR).
*/
XCanPs_SetBaudRatePrescaler(InstancePtr, BRPR_BAUD_PRESCALAR);//设定波特率
XCanPs_SetBitTiming(InstancePtr, BTR_SYNCJUMPWIDTH,
BTR_SECOND_TIMESEGMENT,
BTR_FIRST_TIMESEGMENT);
}
/**********************************************************************
* 函数名称: Can_Ps_Init
* 功能描述: 配置ps can初始化
* 输入参数: null
* 输出参数: 无
* 返 回 值: Status
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2024/05/11 V1.0 DEMO 创建
***********************************************************************/
static int Can_Ps_Init(XScuGic *IntcInstPtr ,XCanPs *CanInstPtr,
u16 CanDeviceId, u16 CanIntrId)
{
int Status;
XCanPs_Config *ConfigPtr;
ConfigPtr = XCanPs_LookupConfig(CanDeviceId);//查找设备
if (ConfigPtr == NULL) {
return XST_FAILURE;
}
XCanPs_CfgInitialize(CanInstPtr,
ConfigPtr,
ConfigPtr->BaseAddr);//配置初始化
Status = XCanPs_SelfTest(CanInstPtr);//自检
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
Can_Ps_Config(CanInstPtr);//配置can控制器
XCanPs_SetHandler(CanInstPtr, XCANPS_HANDLER_SEND,
(void *)SendHandler, (void *)CanInstPtr);//设置发送回调
XCanPs_SetHandler(CanInstPtr, XCANPS_HANDLER_RECV,
(void *)RecvHandler, (void *)CanInstPtr);//设置接收回调
XCanPs_SetHandler(CanInstPtr, XCANPS_HANDLER_ERROR,
(void *)ErrorHandler, (void *)CanInstPtr);//设置错误回调
XCanPs_SetHandler(CanInstPtr, XCANPS_HANDLER_EVENT,
(void *)EventHandler, (void *)CanInstPtr);//设置一般事件回调
/*
* Initialize the flags.
*/
SendDone = FALSE;
RecvDone = FALSE;
/*
* Connect to the interrupt controller.
*/
Status = XScuGic_Connect(IntcInstPtr, CanIntrId,
(Xil_InterruptHandler)XCanPs_IntrHandler,
(void *)CanInstPtr);//连接中断处理函数
if (Status != XST_SUCCESS) {
return Status;
}
/*
* Enable the interrupt for the CAN device.
*/
XScuGic_Enable(IntcInstPtr, CanIntrId);//使能can的系统中断
/*
* Enable all interrupts in CAN device.
*/
XCanPs_IntrEnable(CanInstPtr, XCANPS_IXR_ALL);//使能can的所有中断
/*
* Enter Loop Back Mode.
*/
XCanPs_EnterMode(CanInstPtr, CAN_MODE);//内部回环模式
while(XCanPs_GetMode(CanInstPtr) != CAN_MODE);
return XST_SUCCESS;
}
/**********************************************************************
* 函数名称: SendHandler
* 功能描述: 发送回调函数
* 输入参数: null
* 输出参数: null
* 返 回 值: null
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2024/05/11 V1.0 DEMO 创建
***********************************************************************/
static void SendHandler(void *CallBackRef)
{
/*
* The frame was sent successfully. Notify the task context.
*/
SendDone = TRUE;
//printf("CAN_0 DATA SendDone\r\n\r\n");
}
/**********************************************************************
* 函数名称: RecvHandler
* 功能描述: 接收回调函数,对数据进行解析
* 输入参数: null
* 输出参数: null
* 返 回 值: null
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2024/05/11 V1.0 DEMO 创建
***********************************************************************/
static void RecvHandler(void *CallBackRef)
{
XCanPs *CanPtr = (XCanPs *)CallBackRef;
int Status;
u32 Rx_temp = 0;
unsigned long MESSAGE_ID;
unsigned long IDEXTENSION;
unsigned long DATA_LENGTH;
unsigned char RECV_DATA[8];
/*
* 读取
*/
Status = XCanPs_Recv(CanPtr, RxFrame);
if (Status != XST_SUCCESS) {
return;
}
Rx_temp = RxFrame[0];//帧0
/*
* 解析帧类型,0标准帧,1扩展帧
*/
IDEXTENSION = (Rx_temp & XCANPS_IDR_IDE_MASK)>>XCANPS_IDR_IDE_SHIFT;
/*
* 解析消息ID
*/
if(IDEXTENSION == 0)
{
MESSAGE_ID = (Rx_temp & XCANPS_IDR_ID1_MASK)>>XCANPS_IDR_ID1_SHIFT;
}
else if(IDEXTENSION == 1)
{
uint32_t standardId = (Rx_temp & XCANPS_IDR_ID1_MASK) >> XCANPS_IDR_ID1_SHIFT;
uint32_t extendedId = (Rx_temp & XCANPS_IDR_ID2_MASK) >> XCANPS_IDR_ID2_SHIFT;
MESSAGE_ID = (standardId << 18) | extendedId;
}
/*
* 解析数据长度
*/
Rx_temp = RxFrame[1];
DATA_LENGTH = (Rx_temp & XCANPS_DLCR_DLC_MASK)>>XCANPS_DLCR_DLC_SHIFT;
/*
* 解析数据
*/
u8 *FramePtr;
FramePtr = (u8 *)(&RxFrame[2]);
for (int Index = 0; Index < DATA_LENGTH; Index++) {
RECV_DATA[Index] = FramePtr[Index];
}
RecvDone = TRUE;
/*
* 数据帧解析完毕,执行用户操作
* */
Ps_CAN_0_Recv( MESSAGE_ID,
DATA_LENGTH,
RECV_DATA);
}
/**********************************************************************
* 函数名称: ErrorHandler
* 功能描述: 异常回调函数
* 输入参数: null
* 输出参数: null
* 返 回 值: null
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2024/05/11 V1.0 DEMO 创建
***********************************************************************/
static void ErrorHandler(void *CallBackRef, u32 ErrorMask)
{
if(ErrorMask & XCANPS_ESR_ACKER_MASK) {
/*
* ACK Error handling code should be put here.
*/
}
if(ErrorMask & XCANPS_ESR_BERR_MASK) {
/*
* Bit Error handling code should be put here.
*/
}
if(ErrorMask & XCANPS_ESR_STER_MASK) {
/*
* Stuff Error handling code should be put here.
*/
}
if(ErrorMask & XCANPS_ESR_FMER_MASK) {
/*
* Form Error handling code should be put here.
*/
}
if(ErrorMask & XCANPS_ESR_CRCER_MASK) {
/*
* CRC Error handling code should be put here.
*/
}
}
/**********************************************************************
* 函数名称: EventHandler
* 功能描述: 事件回调函数
* * interrupts:
* - XCANPS_IXR_BSOFF_MASK: Bus Off Interrupt
* - XCANPS_IXR_RXOFLW_MASK: RX FIFO Overflow Interrupt
* - XCANPS_IXR_RXUFLW_MASK: RX FIFO Underflow Interrupt
* - XCANPS_IXR_TXBFLL_MASK: TX High Priority Buffer Full Interrupt
* - XCANPS_IXR_TXFLL_MASK: TX FIFO Full Interrupt
* - XCANPS_IXR_WKUP_MASK: Wake up Interrupt
* - XCANPS_IXR_SLP_MASK: Sleep Interrupt
* - XCANPS_IXR_ARBLST_MASK: Arbitration Lost Interrupt
* 输入参数: null
* 输出参数: null
* 返 回 值: null
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2024/05/11 V1.0 DEMO 创建
***********************************************************************/
static void EventHandler(void *CallBackRef, u32 IntrMask)
{
XCanPs *CanPtr = (XCanPs *)CallBackRef;
if (IntrMask & XCANPS_IXR_BSOFF_MASK) {
/*
* Entering Bus off status interrupt requires
* the CAN device be reset and reconfigured.
*/
XCanPs_Reset(CanPtr);
Can_Ps_Config(CanPtr);
return;
}
if(IntrMask & XCANPS_IXR_RXOFLW_MASK) {
/*
* Code to handle RX FIFO Overflow Interrupt should be put here.
*/
}
if(IntrMask & XCANPS_IXR_RXUFLW_MASK) {
/*
* Code to handle RX FIFO Underflow Interrupt
* should be put here.
*/
}
if(IntrMask & XCANPS_IXR_TXBFLL_MASK) {
/*
* Code to handle TX High Priority Buffer Full
* Interrupt should be put here.
*/
}
if(IntrMask & XCANPS_IXR_TXFLL_MASK) {
/*
* Code to handle TX FIFO Full Interrupt should be put here.
*/
}
if (IntrMask & XCANPS_IXR_WKUP_MASK) {
/*
* Code to handle Wake up from sleep mode Interrupt
* should be put here.
*/
}
if (IntrMask & XCANPS_IXR_SLP_MASK) {
/*
* Code to handle Enter sleep mode Interrupt should be put here.
*/
}
if (IntrMask & XCANPS_IXR_ARBLST_MASK) {
/*
* Code to handle Lost bus arbitration Interrupt
* should be put here.
*/
}
}
/**********************************************************************
* 函数名称: Ps_CAN_Send
* 功能描述: can发送数据
* 输入参数: unsigned long MESSAGE_ID,消息id
unsigned long IDEXTENSION,定义是扩展帧还是标准帧
unsigned long DATA_LENGTH,数据长度,最小1,最大8
unsigned char *SEND_DATA,发送的数据
* 输出参数: 无
* 返 回 值: Status
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2024/05/11 V1.0 DEMO 创建
***********************************************************************/
int Ps_CAN_0_Send(unsigned long MESSAGE_ID,
unsigned long IDEXTENSION,
unsigned long DATA_LENGTH,
unsigned char *SEND_DATA)
{
int Status;
/*
* XCanPs_CreateIdValue 生成CAN帧的标识符
* para1:标准消息ID值,11位
* para2:替代远程传输请求值(SRR)
* para3:标识符扩展(IDE),指示是否使用扩展ID,0标准帧,1扩展帧
* para4:扩展消息ID值,最高29位
* para5:远程传输请求(RTR),用于指示该帧是否为远程帧
*/
if(IDEXTENSION == 0)
{
if( MESSAGE_ID > MAX_StandardId )
{
Status = -1;
return Status;
}
TxFrame[0] = (u32)XCanPs_CreateIdValue( MESSAGE_ID,
0,
IDEXTENSION,
0, //扩展id
0);
}
else if(IDEXTENSION == 1)
{
if( MESSAGE_ID > MAX_ExtendedId )
{
Status = -1;
return Status;
}
TxFrame[0] = (u32)XCanPs_CreateIdValue( (MESSAGE_ID >> 18) & 0x7FF,//需要把id拆分成标准帧并传入
0,
IDEXTENSION,
MESSAGE_ID & 0x3FFFF, //需要把id拆分把标准帧部分剔除并传入剩余数据位
0);
}
/*
* 设置数据长度
*/
if((DATA_LENGTH < MIN_DATA_LENGTH)||(DATA_LENGTH > MAX_DATA_LENGTH ))
{
Status = -1;
return Status;
}
TxFrame[1] = (u32)XCanPs_CreateDlcValue((u32)DATA_LENGTH);
/*
* 数据写入数组
*/
u8 *FramePtr;
FramePtr = (u8 *)(&TxFrame[2]);
for (int Index = 0; Index < DATA_LENGTH; Index++) {
*FramePtr++ = *SEND_DATA ++;
}
/*
* 等待发送
*/
while (XCanPs_IsTxFifoFull(&CanInstance) == TRUE);
Status = XCanPs_Send(&CanInstance, TxFrame);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
return XST_SUCCESS ;
}
/**********************************************************************
* 函数名称: Ps_CAN_Recv
* 功能描述: 收到数据,进行相关用户处理
* 输入参数: unsigned long MESSAGE_ID,消息id
unsigned long DATA_LENGTH,数据长度,最小1,最大8
unsigned char *SEND_DATA,接收的数据
* 输出参数: 无
* 返 回 值: Status
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2024/05/11 V1.0 DEMO 创建
***********************************************************************/
static void Ps_CAN_0_Recv(unsigned long MESSAGE_ID,
unsigned long DATA_LENGTH,
unsigned char *RECV_DATA)
{
if (can_recv_callback != NULL) {
can_recv_callback(MESSAGE_ID, DATA_LENGTH, RECV_DATA);
}
}
/**********************************************************************
* 函数名称: CAN_Ps_Intr_Init
* 功能描述: can初始化
* 输入参数: XScuGic *IntcInstPtr 系统中断实例
* 输出参数: 无
* 返 回 值: Status
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2024/05/11 V1.0 DEMO 创建
***********************************************************************/
int Ps_CAN_0_Intr_Init(XScuGic *IntcInstPtr)
{
int Status;
Status = Can_Ps_Init(IntcInstPtr ,&CanInstance,
CAN_DEVICE_ID, CAN_INTR_VEC_ID);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
printf("CAN_0 Init Successful!!\r\n\r\n");
return XST_SUCCESS;
}
- 头文件
/*
* dev_can_ps_0.h
*
* Created on: 2024年5月11日
* Author: Jafi666
*/
#ifndef SRC_DEV_CAN_PS_0_H_
#define SRC_DEV_CAN_PS_0_H_
#include "xscugic.h"
int Ps_CAN_0_Intr_Init(XScuGic *IntcInstPtr);
int Ps_CAN_0_Send(unsigned long MESSAGE_ID,
unsigned long IDEXTENSION,
unsigned long DATA_LENGTH,
unsigned char *SEND_DATA);
typedef void (*CAN0_Recv_Callback)(unsigned long MESSAGE_ID, unsigned long DATA_LENGTH, unsigned char *RECV_DATA);//定义一个回调函数类型
void CAN0_Register_Recv_Callback(CAN0_Recv_Callback callback);
#endif /* SRC_DEV_CAN_PS_0_H_ */
- 文件结构与宏配置
宏名 | 说明 |
---|---|
CAN_DEVICE_ID | CAN0 控制器设备 ID(来自 xparameters.h ) |
CAN_INTR_VEC_ID | CAN0 中断向量号 |
CAN_MODE | 当前使用模式(支持 NORMAL 和 LOOPBACK) |
BRPR_BAUD_PRESCALAR | 波特率预分频因子 |
BTR_FIRST_TIMESEGMENT | TSEG1 时间段 |
BTR_SECOND_TIMESEGMENT | TSEG2 时间段 |
BTR_SYNCJUMPWIDTH | SJW 同步跳转宽度 |
当前配置适配 40MHz 时钟频率,目标波特率为 250Kbps
-
核心接口说明
-
int Ps_CAN_0_Intr_Init(XScuGic *IntcInstPtr);
初始化 CAN0 控制器,完成驱动注册、中断绑定及进入目标模式。
-
int Ps_CAN_0_Send(unsigned long id, unsigned long ext, unsigned long len, unsigned char *data);
向 CAN 总线发送一帧标准帧或扩展帧。
-
void CAN0_Register_Recv_Callback(CAN0_Recv_Callback callback);
注册接收回调函数,用户处理接收到的 CAN 帧内容。
-
-
中断机制说明
驱动注册并处理了 4 类中断类型,分别为:
类型 | 对应函数 | 说明 |
---|---|---|
发送完成 | SendHandler() | 设置 SendDone = TRUE |
接收数据 | RecvHandler() | 解析帧 ID/数据长度/数据内容,调用用户回调 |
异常错误 | ErrorHandler() | 处理 ACK/BIT/CRC/STUFF 错误,预留用户扩展 |
总线事件 | EventHandler() | 包含总线关闭、溢出、唤醒等事件处理 |
-
接收数据流程
-
- 中断进入
RecvHandler
- 中断进入
-
- 调用
XCanPs_Recv()
获取数据帧
- 调用
-
- 判断帧类型(标准/扩展),提取 ID
-
- 提取数据长度与有效载荷
-
- 回调函数执行:
can_recv_callback(...)
- 回调函数执行:
-
-
发送数据流程
-
- 用户调用
Ps_CAN_0_Send(...)
- 用户调用
-
- ID编码使用
XCanPs_CreateIdValue
- ID编码使用
-
- 数据写入
TxFrame
,使用XCanPs_Send
发送
- 数据写入
-
SendHandler
中置位SendDone
表示完成
-
-
CAN波特率计算方式
- 计算公式:Bitrate = CAN_CLK / ((BRPR + 1) × (1 + TSEG1 + TSEG2))
CAN 波特率影响因素:时钟源、预分频器、时间段1/2,读者需要根据自己项目所需波特率,灵活调整公式内的参数
- 计算公式:Bitrate = CAN_CLK / ((BRPR + 1) × (1 + TSEG1 + TSEG2))
2.3 裸机中断配置
CAN 想实现收的功能,需要使用中断,对中断控制器 GIC 初始化,这个例程里我将CAN驱动结合在中断初始化中
-
中断注册方法
将can 初始化函数放置在 static int Interrupt_Devices_Init(XScuGic *IntcInstancePtr) 函数中 -
源码
/*
* interrupt.c
*
* Created on: 2024年5月11日
* Author: Jafi666
*/
#include "interrupt.h"
#include "stdio.h"
#include "xscugic.h"
#include "dev_can_ps_0.h"
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
static XScuGic IntcInstance; /* Instance of the Interrupt Controller driver */
/**********************************************************************
* 函数名称: Interrupt_Devices_Init
* 功能描述: 设备若需要使用中断,在此注册
* 输入参数: XScuGic *IntcInstancePtr 系统中断实例指针
* 输出参数: 无
* 返 回 值: Status
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2024/05/11 V1.0 DEMO 创建
***********************************************************************/
static int Interrupt_Devices_Init(XScuGic *IntcInstancePtr)
{
int Status;
Status = Ps_CAN_0_Intr_Init(IntcInstancePtr);//注册初始化can0
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
return XST_SUCCESS;
}
/**********************************************************************
* 函数名称: SetupInterruptSystem
* 功能描述: 设置系统中断
* 输入参数: XScuGic *IntcInstancePtr 系统中断实例指针
* 输出参数: 无
* 返 回 值: Status
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2024/05/11 V1.0 DEMO 创建
***********************************************************************/
static int SetupInterruptSystem(XScuGic *IntcInstancePtr)
{
int Status;
XScuGic_Config *IntcConfig; /* Instance of the interrupt controller *///
Xil_ExceptionInit();
IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);//查找系统中断控制器
if (NULL == IntcConfig) {
return XST_FAILURE;
}
Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig,//配置系统中断实例
IntcConfig->CpuBaseAddress);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_IRQ_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,
IntcInstancePtr);//注册特定的异常处理器
Status = Interrupt_Devices_Init(IntcInstancePtr);//设备若需要使用中断,在此注册
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
Xil_ExceptionEnable();//使能系统处理器中断
return XST_SUCCESS;
}
/**********************************************************************
* 函数名称: Interrupt_Init
* 功能描述: 中断初始化
* 输入参数: null
* 输出参数: 无
* 返 回 值: Status
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2024/05/11 V1.0 DEMO 创建
***********************************************************************/
int Interrupt_Init(void)
{
int Status;
Status = SetupInterruptSystem(&IntcInstance);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
return XST_SUCCESS;
}
- 头文件
/*
* interrupt.h
*
* Created on: 2024年5月11日
* Author: Jafi666
*/
#ifndef SRC_INTERRUPT_H_
#define SRC_INTERRUPT_H_
int Interrupt_Init(void);
#endif /* SRC_INTERRUPT_H_ */
2.4 裸机使用流程
- 初始化
print("CAN DEMO\n\r\n\r");
Status = Interrupt_Init();//中断初始化,CAN 也在里面初始化
if (Status != XST_SUCCESS) {
xil_printf("Interrupt_InitFailed\r\n");
return XST_FAILURE;
}
- 发送流程
unsigned char data[8];
xil_printf("CAN_0 TEST START*********************\r\n");
data[0] = 0x08;
data[1] = 0x07;
data[2] = 0x06;
data[3] = 0x05;
data[4] = 0x04;
data[5] = 0x03;
data[6] = 0x02;
data[7] = 0x01;
Ps_CAN_0_Send(0x42,/*id*/
1,/*使用扩展帧*/
8,/*数据长度*/
&data[0]);//can0 发送测试
- 接收处理
/**********************************************************************
* 函数名称: CAN0_Recv_Handler
* 功能描述: can0中断回调函数
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 无
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2024/05/26 V1.0 Jafi 创建
***********************************************************************/
void CAN0_Recv_Handler(unsigned long MESSAGE_ID, unsigned long DATA_LENGTH, unsigned char *RECV_DATA) {
printf("\r\nCAN_0 MESSAGE_ID= %d\r\n",(int)MESSAGE_ID);
printf("CAN_0 DATA_LENGTH= %d\r\n",(int)DATA_LENGTH);
for (int Index = 0; Index < DATA_LENGTH; Index++) {
printf("CAN_0 RECV_DATA[%d] = %d\r\n",(u8)Index, RECV_DATA[Index]);
}
}
/**/
CAN0_Register_Recv_Callback(CAN0_Recv_Handler);//主程序内注册回调就行
2.5 FreeRTOS使用说明
- 移植 xilinx 的freertos包搭建工程的情况下,rtos内部会完成中断注册初始化,如果我们需要增加器件中断,千万不要重新 申请注册 XScuGic 系统中断实例,重复注册系统中断实例将导致先注册的参数被刷,导致中断异常。可找到freertos内申请的XScuGic 实例extern出来,将器件connect上去
- 具体示例:
extern XScuGic xInterruptController;//系统中断(freertos已初始化)
/**********************************************************************
* 函数名称: Interrupt_Devices_Init
* 功能描述: 设备若需要使用中断,在此注册
* 输入参数: XScuGic *IntcInstancePtr 系统中断实例指针
* 输出参数: 无
* 返 回 值: Status
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2024/05/11 V1.0 DEMO 创建
***********************************************************************/
int Driver_Init(void)
{
int Status;
Status = Ps_CAN_0_Intr_Init(&xInterruptController);//注册初始化can0
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
return XST_SUCCESS;
}
void freertos_task_init(void)
{
xTaskCreate(START_TASK, "START_TASK", START_STK_SIZE, NULL, START_TASK_PRIO, &START_TASK_Handler);
/* start scheduler */
vTaskStartScheduler();//这里面将启动freertos所有配置,包括系统中断,之后不允许注册系统中断器(可添加设备中断),否则将导致系统错误
Driver_Init();
}
2.6 预期效果
- 注:这里我贴的是1Mbps的通信,用的是CAN1,效果都是一样的,只是为了展示一个结果,250Kbps的忘记截图了
3. 调试建议
3.1 CAN调试中的常见问题
-
无法发送数据:
- 仲裁失败:ID冲突或总线繁忙
- 硬件接线错误:CANH/CANL未正确连接或无终端电阻
-
无法接收数据:
- 中断未使能或未正确配置
- 接收缓冲区未清空,或未调用接收函数
-
收发不稳定:
- 波特率设置不一致
- 总线长度过长或干扰严重
- 电源干扰或收发器损坏
3.2 Zynq调试中的常见问题
-
zynq与其他的一些平台有一些差异,调试过程中可能会遇到一些问题,比如波特率相关参数的配比,可以先计算出大概,然后多试试几个参数
-
中断无响应:
- 未启用中断源(如GIC、CAN控制器)
- 优先级设置错误或中断屏蔽未清除
- 检查GIC初始化及
EnableInterrupts()
代码是否完整,是否重复初始化
3.3 调试建议与工具推荐
-
推荐CAN调试设备:
- USB-CAN分析仪(如 CANalyst-II),尽量选择好一点的收发器,有些收发器特别坑,如识别不到波特率,指令解析出错等等问题!
- 上位机工具(如 CANTool)辅助验证数据格式
-
调试技巧:
- 初期建议使用内部回环模式验证工程
结语
本文围绕 Zynq 平台上的 CAN 通信开发进行了介绍,内容涵盖 CAN 协议基础、平台配置注意事项、调试方法等。希望对正在进行 Zynq 开发、尤其是需要实现裸机或RTOS下 CAN 通信功能的工程师提供一定的参考价值。
如果你有更深入的问题,欢迎留言交流,后续我也将继续更新更多内容,读者若发现文章中有错误的地方,也欢迎评论私信讨论~
作者:Jafi