Zynq CAN开发实战指南+驱动源码分享

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
    • 引脚受限于板载资源,需查阅原理图确认是否可用
  • EMIO引脚(推荐用于 PL 引脚输出):

    • 可通过 EMIO 将 CAN 信号输出到 PL,再通过 IO Buffer 引脚约束输出至收发器
    • 适用于 MIO 不足或 MIO已被占用的情况
  • 注意事项:

    • 收发器建议独立供电(推荐3.3V或5V)
    • 与其他设备通信时,需确认双方波特率一致

2. 具体上手

2.1 工程配置

  • ** Vivado 中的硬件平台设计**
    • BlockdesignBlockdesign

    • 使能CAN,共有两个CAN,根据自己需求使能
      使能CAN

    • 配置时钟。关于时钟,官方例程24M,我个人调整为40M
      clock

    • 管脚XDC约束,根据个人需求,我这边使用EMIO
      XDC

  • XSDK
    • 这里不介绍工程建立,可参照官方源码修改
      mss
      example

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_IDCAN0 控制器设备 ID(来自 xparameters.h
CAN_INTR_VEC_IDCAN0 中断向量号
CAN_MODE当前使用模式(支持 NORMAL 和 LOOPBACK)
BRPR_BAUD_PRESCALAR波特率预分频因子
BTR_FIRST_TIMESEGMENTTSEG1 时间段
BTR_SECOND_TIMESEGMENTTSEG2 时间段
BTR_SYNCJUMPWIDTHSJW 同步跳转宽度

当前配置适配 40MHz 时钟频率,目标波特率为 250Kbps

  • 核心接口说明

      1. int Ps_CAN_0_Intr_Init(XScuGic *IntcInstPtr);
        初始化 CAN0 控制器,完成驱动注册、中断绑定及进入目标模式。
      1. int Ps_CAN_0_Send(unsigned long id, unsigned long ext, unsigned long len, unsigned char *data);
        向 CAN 总线发送一帧标准帧或扩展帧。
      1. void CAN0_Register_Recv_Callback(CAN0_Recv_Callback callback);
        注册接收回调函数,用户处理接收到的 CAN 帧内容。
  • 中断机制说明
    驱动注册并处理了 4 类中断类型,分别为:

类型对应函数说明
发送完成SendHandler()设置 SendDone = TRUE
接收数据RecvHandler()解析帧 ID/数据长度/数据内容,调用用户回调
异常错误ErrorHandler()处理 ACK/BIT/CRC/STUFF 错误,预留用户扩展
总线事件EventHandler()包含总线关闭、溢出、唤醒等事件处理
  • 接收数据流程

      1. 中断进入 RecvHandler
      1. 调用 XCanPs_Recv() 获取数据帧
      1. 判断帧类型(标准/扩展),提取 ID
      1. 提取数据长度与有效载荷
      1. 回调函数执行:can_recv_callback(...)
  • 发送数据流程

      1. 用户调用 Ps_CAN_0_Send(...)
      1. ID编码使用 XCanPs_CreateIdValue
      1. 数据写入 TxFrame,使用 XCanPs_Send 发送
      1. SendHandler 中置位 SendDone 表示完成
  • CAN波特率计算方式

    • 计算公式:Bitrate = CAN_CLK / ((BRPR + 1) × (1 + TSEG1 + TSEG2))
      CAN 波特率影响因素:时钟源、预分频器、时间段1/2,读者需要根据自己项目所需波特率,灵活调整公式内的参数

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


评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值