手把手教你用Xilinx SDK调试Zynq-7000的PS和PL端CAN总线(附波特率计算与宇泰CAN卡对接)
深入实战:Xilinx SDK环境下Zynq-7000双CAN总线开发全流程解析
在嵌入式系统开发中,CAN总线因其高可靠性和实时性被广泛应用于工业控制、汽车电子等领域。Zynq-7000系列作为Xilinx的明星产品,集成了ARM处理器(PS)和可编程逻辑(PL),为CAN总线应用提供了灵活的实现方案。本文将带您从硬件连接到软件调试,完整掌握PS端和PL端CAN总线的开发流程。
1. 开发环境准备与基础概念
工欲善其事,必先利其器。在开始CAN总线开发前,我们需要确保开发环境配置正确,并理解一些关键概念。
硬件准备清单:
- Zynq-7000开发板(如ZC702、Zybo等)
- 宇泰USB-CAN适配器(如UT-8251)
- CAN总线终端电阻(120Ω)
- 杜邦线等连接线材
软件工具链:
- Xilinx Vivado Design Suite(含SDK)
- 宇泰CAN上位机软件
- 串口终端工具(如Tera Term、Putty)
注意:确保所有硬件设备共地,避免因电位差导致通信异常。
CAN总线在Zynq中的实现有两种方式:
- PS端CAN:使用Zynq处理器子系统内置的CAN控制器
- PL端CAN:通过可编程逻辑实现的CAN控制器(如Xilinx AXI CAN IP)
两者主要差异在于时钟源和配置方式:
| 特性 | PS端CAN | PL端CAN |
|---|---|---|
| 时钟源 | 固定为100MHz | 由PL时钟决定 |
| 配置方式 | 通过PS寄存器配置 | 通过AXI接口配置 |
| 资源占用 | 不占用PL资源 | 需要消耗PL逻辑资源 |
| 灵活性 | 功能固定 | 可定制化程度高 |
2. PS端CAN总线配置与调试
PS端CAN作为Zynq内置外设,配置相对简单但需要注意波特率计算的精确性。
2.1 硬件连接与工程创建
首先完成硬件连接:
- 将开发板PS端CAN接口(通常为MIO引脚)连接到CAN收发器
- CAN收发器通过双绞线连接到宇泰USB-CAN适配器
- 确保总线两端都有120Ω终端电阻
在SDK中创建裸机工程的步骤:
1. 新建Application Project 2. 选择"Empty Application"模板 3. 在system.mss文件中导入CAN Polled Example2.2 波特率精确计算
PS端CAN时钟固定为100MHz,波特率计算公式为:
波特率 = CAN时钟频率 / (BRPR + 1) / (Sync_Seg + Prop_Seg + Phase_Seg1 + Phase_Seg2)其中:
- BRPR:波特率预分频值
- Sync_Seg:固定为1个时间量子
- Prop_Seg + Phase_Seg1 = TSEG1
- Phase_Seg2 = TSEG2
以配置100kbps波特率为例:
#define PS_CAN_CLOCK 100000000 // 100MHz #define BRPR_VALUE 49 #define TSEG1 15 // Prop_Seg + Phase_Seg1 #define TSEG2 2 // Phase_Seg2 #define SYNC_JUMP_WIDTH 3 uint32_t baud_rate = PS_CAN_CLOCK / ((BRPR_VALUE + 1) * (1 + TSEG1 + TSEG2)); // 计算结果:100MHz / (50 * 18) ≈ 100kbps2.3 关键代码实现
以下是PS端CAN初始化和配置的核心代码:
XCanPs_Config *CanConfig; XCanPs CanInstance; int can_init(u16 device_id) { // 查找CAN控制器配置 CanConfig = XCanPs_LookupConfig(device_id); if (CanConfig == NULL) return XST_FAILURE; // 初始化CAN控制器 int status = XCanPs_CfgInitialize(&CanInstance, CanConfig, CanConfig->BaseAddr); if (status != XST_SUCCESS) return status; // 自检 status = XCanPs_SelfTest(&CanInstance); if (status != XST_SUCCESS) return status; // 进入配置模式设置波特率 XCanPs_EnterMode(&CanInstance, XCANPS_MODE_CONFIG); while(XCanPs_GetMode(&CanInstance) != XCANPS_MODE_CONFIG); // 设置波特率参数 XCanPs_SetBaudRatePrescaler(&CanInstance, BRPR_VALUE); XCanPs_SetBitTiming(&CanInstance, SYNC_JUMP_WIDTH, TSEG2, TSEG1); // 进入正常工作模式 XCanPs_EnterMode(&CanInstance, XCANPS_MODE_NORMAL); return XST_SUCCESS; }3. PL端CAN总线实现要点
PL端CAN通过AXI CAN IP核实现,配置更为灵活但也更复杂。
3.1 Vivado中的IP核配置
在Vivado中配置AXI CAN IP核的关键步骤:
- 创建Block Design,添加Zynq Processing System
- 添加AXI CAN Controller IP核
- 配置IP核参数:
- 时钟频率(需与PL端实际时钟一致)
- AXI接口类型(建议使用AXI Lite)
- 中断设置(如需要)
提示:PL端CAN时钟通常由PL fabric提供,需在Vivado中确认具体频率值。
3.2 波特率计算差异
PL端CAN波特率计算原理与PS端相同,但时钟源不同。例如,若PL端CAN时钟为50MHz,要配置125kbps波特率:
#define PL_CAN_CLOCK 50000000 // 50MHz #define BRPR_VALUE 19 #define TSEG1 15 #define TSEG2 4 uint32_t baud_rate = PL_CAN_CLOCK / ((BRPR_VALUE + 1) * (1 + TSEG1 + TSEG2)); // 50MHz / (20 * 20) = 125kbps3.3 PL端CAN代码实现
PL端CAN的软件实现与PS端类似,但使用不同的驱动API:
#include "xcan.h" XCAN CanPlInstance; XCAN_Config *CanPlConfig; int can_pl_init(u16 device_id) { // 初始化CAN控制器 CanPlConfig = XCan_LookupConfig(device_id); if (CanPlConfig == NULL) return XST_FAILURE; int status = XCan_CfgInitialize(&CanPlInstance, CanPlConfig, CanPlConfig->BaseAddr); if (status != XST_SUCCESS) return status; // 设置波特率 XCan_SetBaudRatePrescaler(&CanPlInstance, BRPR_VALUE); XCan_SetBitTiming(&CanPlInstance, SYNC_JUMP_WIDTH, TSEG2, TSEG1); // 启动CAN控制器 XCan_Start(&CanPlInstance); return XST_SUCCESS; }4. 双CAN总线调试技巧与宇泰设备对接
实际项目中经常需要同时调试PS和PL端CAN总线,并与第三方设备如宇泰CAN适配器进行通信。
4.1 双CAN总线同步调试
调试双CAN总线时需要注意:
- 为每个CAN接口分配不同的设备ID
- 在代码中明确区分PS和PL端CAN实例
- 使用不同CAN ID避免总线冲突
典型的多CAN初始化代码结构:
// PS端CAN初始化 XCanPs CanPsInstance; int status = can_ps_init(XPAR_XCANPS_0_DEVICE_ID, &CanPsInstance); // PL端CAN初始化 XCAN CanPlInstance; status |= can_pl_init(XPAR_XCAN_0_DEVICE_ID, &CanPlInstance); if (status != XST_SUCCESS) { xil_printf("CAN初始化失败!\r\n"); return XST_FAILURE; }4.2 与宇泰CAN适配器对接
宇泰USB-CAN适配器是常用的调试工具,对接时需注意:
- 确保双方波特率设置完全一致
- 正确设置帧格式(标准帧/扩展帧)
- 匹配CAN ID过滤设置
典型的数据收发测试流程:
- 开发板发送测试帧到宇泰适配器
XCAN_Frame txFrame; txFrame.Id = 0x123; // CAN ID txFrame.Dlc = 8; // 数据长度 memcpy(txFrame.Data, "TestData", 8); // 数据内容 XCan_Send(&CanPlInstance, &txFrame);- 使用宇泰上位机发送回复帧
- 开发板接收并打印数据
XCAN_Frame rxFrame; if (XCan_Receive(&CanPlInstance, &rxFrame) == XST_SUCCESS) { xil_printf("收到CAN帧: ID=0x%x, 数据=", rxFrame.Id); for (int i = 0; i < rxFrame.Dlc; i++) { xil_printf("%02x ", rxFrame.Data[i]); } xil_printf("\r\n"); }4.3 常见问题排查
在实际调试中常遇到的问题及解决方法:
无法通信:
- 检查物理连接和终端电阻
- 确认双方波特率设置一致
- 使用示波器检查CAN总线信号
数据错误:
- 检查CAN ID设置
- 确认帧格式(标准/扩展)匹配
- 验证数据字节序
性能问题:
- 优化CAN中断处理
- 考虑使用DMA传输
- 调整CAN控制器工作模式(如启用自接收测试)
5. 进阶应用与性能优化
掌握了基础CAN通信后,可以进一步优化系统性能和可靠性。
5.1 中断驱动 vs 轮询模式
根据应用需求选择合适的通信模式:
| 模式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 轮询 | 实现简单 | CPU占用率高 | 低频率简单应用 |
| 中断驱动 | 实时性好,CPU占用低 | 实现复杂 | 高实时性要求应用 |
| DMA传输 | 最高效,CPU干预最少 | 配置最复杂 | 大数据量传输 |
中断模式初始化示例:
// 设置中断系统 XScuGic_InterruptMaptoCpu(XPAR_SCUGIC_SINGLE_DEVICE_ID, XPAR_CPU_ID); XScuGic_RegisterHandler(XPAR_SCUGIC_SINGLE_DEVICE_ID, XPAR_FABRIC_AXI_CAN_0_INTERRUPT_INTR, (Xil_ExceptionHandler)can_interrupt_handler, &CanInstance); // 启用CAN接收中断 XCan_EnableIntr(&CanInstance, XCAN_IXR_RXOK_MASK); XScuGic_EnableIntr(XPAR_SCUGIC_SINGLE_DEVICE_ID, XPAR_FABRIC_AXI_CAN_0_INTERRUPT_INTR);5.2 CAN总线错误处理
完善的错误处理机制能提高系统鲁棒性:
uint32_t error_status = XCan_GetErrorStatus(&CanInstance); if (error_status & XCAN_ESR_BOFF_MASK) { // 总线关闭状态处理 XCan_Reset(&CanInstance); } else if (error_status & XCAN_ESR_EPASS_MASK) { // 错误被动状态处理 } else if (error_status & XCAN_ESR_EWARN_MASK) { // 错误警告状态处理 }5.3 多CAN总线负载均衡
对于需要处理大量CAN数据的应用,可以考虑:
- 根据消息优先级分配不同CAN总线
- 实现简单的负载均衡算法
- 使用硬件过滤器减少CPU开销
// 硬件过滤器配置示例 XCan_Filter filter; filter.Id = 0x100; // 要过滤的CAN ID filter.Mask = 0x700; // 掩码 filter.Fifo = XCAN_FIFO0; // 指定接收FIFO XCan_SetFilter(&CanInstance, &filter);在项目实践中发现,PL端CAN由于时钟灵活性,更适合需要特殊波特率的应用场景,而PS端CAN则适合标准通信需求。调试双CAN系统时,建议先单独测试每个CAN接口,确认工作正常后再进行集成测试。
